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

Proposed by Gustavo Pichorim Boiko
Status: Merged
Approved by: Tiago Salem Herrmann
Approved revision: 252
Merged at revision: 233
Proposed branch: lp:history-service/staging
Merge into: lp:history-service
Diff against target: 4547 lines (+2269/-272)
62 files modified
CMakeLists.txt (+1/-0)
Ubuntu/History/historyeventmodel.cpp (+17/-0)
Ubuntu/History/historyeventmodel.h (+2/-0)
Ubuntu/History/historymodel.cpp (+144/-24)
Ubuntu/History/historymodel.h (+48/-2)
Ubuntu/History/historythreadmodel.cpp (+9/-1)
Ubuntu/History/historythreadmodel.h (+3/-1)
daemon/CMakeLists.txt (+1/-0)
daemon/HistoryService.xml (+14/-0)
daemon/historydaemon.cpp (+613/-55)
daemon/historydaemon.h (+28/-9)
daemon/historyservicedbus.cpp (+25/-17)
daemon/historyservicedbus.h (+6/-1)
daemon/rolesinterface.cpp (+71/-0)
daemon/rolesinterface.h (+210/-0)
daemon/textchannelobserver.cpp (+3/-1)
daemon/textchannelobserver.h (+2/-1)
plugins/sqlite/schema/v13.sql (+44/-0)
plugins/sqlite/schema/v14.sql (+82/-0)
plugins/sqlite/schema/v15.sql (+1/-0)
plugins/sqlite/schema/v16.sql (+2/-0)
plugins/sqlite/schema/v17.sql (+2/-0)
plugins/sqlite/sqlitedatabase.cpp (+55/-1)
plugins/sqlite/sqlitedatabase.h (+1/-0)
plugins/sqlite/sqlitehistoryplugin.cpp (+478/-44)
plugins/sqlite/sqlitehistoryplugin.h (+10/-1)
plugins/sqlite/sqlitehistorythreadview.cpp (+0/-5)
src/channelobserver.cpp (+2/-1)
src/contactmatcher.cpp (+13/-3)
src/contactmatcher_p.h (+2/-2)
src/manager.cpp (+18/-2)
src/manager.h (+7/-1)
src/managerdbus.cpp (+14/-2)
src/managerdbus_p.h (+7/-1)
src/participant.cpp (+48/-6)
src/participant.h (+7/-2)
src/participant_p.h (+4/-0)
src/plugin.h (+9/-1)
src/plugineventview.cpp (+0/-1)
src/pluginthreadview.cpp (+0/-1)
src/textevent.cpp (+13/-3)
src/textevent.h (+2/-0)
src/textevent_p.h (+2/-0)
src/thread.cpp (+48/-14)
src/thread.h (+8/-2)
src/thread_p.h (+8/-2)
src/types.h (+67/-1)
src/utils.cpp (+28/-3)
src/utils_p.h (+4/-2)
tests/Ubuntu.History/HistoryEventModelTest.cpp (+1/-0)
tests/Ubuntu.History/HistoryGroupedThreadsModelTest.cpp (+1/-1)
tests/common/mock/CMakeLists.txt (+1/-1)
tests/common/mock/callchannel.cpp (+2/-1)
tests/common/mock/mockconnectiondbus.cpp (+5/-6)
tests/common/mock/textchannel.cpp (+6/-4)
tests/common/mock/textchannel.h (+1/-1)
tests/daemon/CMakeLists.txt (+4/-4)
tests/daemon/DaemonTest.cpp (+2/-2)
tests/libhistoryservice/ParticipantTest.cpp (+18/-4)
tests/libhistoryservice/TextEventTest.cpp (+26/-17)
tests/libhistoryservice/ThreadTest.cpp (+4/-2)
tests/plugins/sqlite/SqlitePluginTest.cpp (+15/-16)
To merge this branch: bzr merge lp:history-service/staging
Reviewer Review Type Date Requested Status
Roberto Mier Escandon (community) Approve
Tiago Salem Herrmann (community) Approve
Review via email: mp+308933@code.launchpad.net

Commit message

Improve group chat support.

Description of the change

Improve group chat support.

To post a comment you must log in.
Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

Left some comments

review: Needs Fixing
lp:history-service/staging updated
241. By Gustavo Pichorim Boiko

Re-enable DaemonTests.

242. By Gustavo Pichorim Boiko

Adapt the registration of objects and services to the way QtDBus works from 5.6.x on.

243. By Gustavo Pichorim Boiko

Skip self join notification in conversation when account is a ofono one

244. By Gustavo Pichorim Boiko

Fix tests on latest tp-qt.

245. By Gustavo Pichorim Boiko

Leave the mock call channel opened for a bit longer to make sure the last state change propagates correctly.

246. By Gustavo Pichorim Boiko

Allow applications to insert different kind of information events.

247. By Gustavo Pichorim Boiko

Update existing chats to Room or None based on the MMS option in Accounts Service.

248. By Gustavo Pichorim Boiko

- Create hash for threadId of broadcast messages
- Avoid grouping chats with different chatType's

249. By Gustavo Pichorim Boiko

- Fix broken tests with latest changes.
- Set ChatType correctly when using createThreadForParticipants()

Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

just two more fixes.

review: Needs Fixing
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Debug prints removed, question answered and loops changed on a separate MR.

lp:history-service/staging updated
250. By Gustavo Pichorim Boiko

Remove debug prints.

251. By Gustavo Pichorim Boiko

Use compareIds()

252. By Gustavo Pichorim Boiko

Use a more complete time format to generate the hash.

Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

Looks good to me. thank you!

review: Approve
lp:history-service/staging updated
253. By Gustavo Pichorim Boiko

Simplify the filtering of participants.

Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

lgtm

review: Approve

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 2015-11-03 14:25:26 +0000
3+++ CMakeLists.txt 2016-11-24 12:52:24 +0000
4@@ -17,6 +17,7 @@
5 find_package(Qt5Qml)
6 find_package(Qt5Quick)
7 find_package(Qt5Test)
8+find_package(Qt5Network)
9 find_package(LibPhoneNumber REQUIRED)
10
11 include(qt5)
12
13=== modified file 'Ubuntu/History/historyeventmodel.cpp'
14--- Ubuntu/History/historyeventmodel.cpp 2015-10-06 12:59:03 +0000
15+++ Ubuntu/History/historyeventmodel.cpp 2016-11-24 12:52:24 +0000
16@@ -45,9 +45,11 @@
17 mRoles[TextMessageAttachmentsRole] = "textMessageAttachments";
18 mRoles[TextReadTimestampRole] = "textReadTimestamp";
19 mRoles[TextReadSubjectRole] = "textSubject";
20+ mRoles[TextInformationTypeRole] = "textInformationType";
21 mRoles[CallMissedRole] = "callMissed";
22 mRoles[CallDurationRole] = "callDuration";
23 mRoles[RemoteParticipantRole] = "remoteParticipant";
24+ mRoles[SubjectAsAliasRole] = "subjectAsAlias";
25 }
26
27 int HistoryEventModel::rowCount(const QModelIndex &parent) const
28@@ -135,6 +137,11 @@
29 result = textEvent.subject();
30 }
31 break;
32+ case TextInformationTypeRole:
33+ if (!textEvent.isNull()) {
34+ result = (int)textEvent.informationType();
35+ }
36+ break;
37 case TextMessageAttachmentsRole:
38 if (!textEvent.isNull()) {
39 if (mAttachmentCache.contains(textEvent)) {
40@@ -164,6 +171,16 @@
41 result = voiceEvent.remoteParticipant();
42 }
43 break;
44+ case SubjectAsAliasRole:
45+ if (!textEvent.isNull()) {
46+ QVariantMap contactInfo = History::ContactMatcher::instance()->contactInfo(event.accountId(), textEvent.subject());
47+ QString returnValue = contactInfo[History::FieldAlias].toString();
48+ if (returnValue.isEmpty()) {
49+ returnValue = contactInfo[History::FieldIdentifier].toString();
50+ }
51+ return returnValue;
52+ }
53+ break;
54 }
55
56 return result;
57
58=== modified file 'Ubuntu/History/historyeventmodel.h'
59--- Ubuntu/History/historyeventmodel.h 2015-10-01 18:25:36 +0000
60+++ Ubuntu/History/historyeventmodel.h 2016-11-24 12:52:24 +0000
61@@ -44,10 +44,12 @@
62 TextMessageStatusRole,
63 TextReadTimestampRole,
64 TextReadSubjectRole,
65+ TextInformationTypeRole,
66 TextMessageAttachmentsRole,
67 CallMissedRole,
68 CallDurationRole,
69 RemoteParticipantRole,
70+ SubjectAsAliasRole,
71 LastEventRole
72 };
73
74
75=== modified file 'Ubuntu/History/historymodel.cpp'
76--- Ubuntu/History/historymodel.cpp 2015-10-08 21:52:59 +0000
77+++ Ubuntu/History/historymodel.cpp 2016-11-24 12:52:24 +0000
78@@ -1,5 +1,5 @@
79 /*
80- * Copyright (C) 2013-2014 Canonical, Ltd.
81+ * Copyright (C) 2013-2016 Canonical, Ltd.
82 *
83 * Authors:
84 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
85@@ -40,7 +40,10 @@
86 mRoles[AccountIdRole] = "accountId";
87 mRoles[ThreadIdRole] = "threadId";
88 mRoles[ParticipantsRole] = "participants";
89+ mRoles[ParticipantsRemotePendingRole] = "remotePendingParticipants";
90+ mRoles[ParticipantsLocalPendingRole] = "localPendingParticipants";
91 mRoles[TypeRole] = "type";
92+ mRoles[TimestampRole] = "timestamp";
93 mRoles[PropertiesRole] = "properties";
94
95 connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
96@@ -89,16 +92,79 @@
97 case TypeRole:
98 result = properties[History::FieldType];
99 break;
100- case ParticipantsRole:
101- if (mMatchContacts) {
102- result = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
103- History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers());
104- } else {
105- //FIXME: handle contact changes
106- result = properties[History::FieldParticipants];
107- }
108- break;
109- }
110+ case ParticipantsRole: {
111+ History::Participants participants = History::Participants::fromVariantList(properties[History::FieldParticipants].toList())
112+ .filterByState(History::ParticipantStateRegular);
113+ if (mMatchContacts) {
114+ QVariantList finalParticipantsList;
115+ QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
116+ participants.identifiers());
117+ for (int i = 0; i < participantsInfo.count(); ++i) {
118+ QVariantMap newMap = participantsInfo[i].toMap();
119+ History::Participant participant = participants[i];
120+ newMap[History::FieldParticipantState] = participant.state();
121+ newMap[History::FieldParticipantRoles] = participant.roles();
122+ finalParticipantsList << newMap;
123+ }
124+ result = finalParticipantsList;
125+ } else {
126+ //FIXME: handle contact changes
127+ result = participants.identifiers();
128+ }
129+ break;
130+ }
131+ case ParticipantsRemotePendingRole: {
132+ History::Participants participants = History::Participants::fromVariantList(properties[History::FieldParticipants].toList())
133+ .filterByState(History::ParticipantStateRemotePending);
134+ if (mMatchContacts) {
135+ QVariantList finalParticipantsList;
136+ QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
137+ participants.identifiers());
138+ int count = 0;
139+ Q_FOREACH(const QVariant &participantInfo, participantsInfo) {
140+ QVariantMap newMap = participantInfo.toMap();
141+ newMap[History::FieldParticipantState] = participants.at(count).state();
142+ newMap[History::FieldParticipantRoles] = participants.at(count++).roles();
143+ finalParticipantsList << newMap;
144+ }
145+ result = finalParticipantsList;
146+ } else {
147+ //FIXME: handle contact changes
148+ result = participants.identifiers();
149+ }
150+
151+ break;
152+ }
153+ case ParticipantsLocalPendingRole: {
154+ History::Participants participants = History::Participants::fromVariantList(properties[History::FieldParticipants].toList())
155+ .filterByState(History::ParticipantStateLocalPending);
156+ if (mMatchContacts) {
157+ QVariantList finalParticipantsList;
158+ QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
159+ participants.identifiers());
160+ int count = 0;
161+ Q_FOREACH(const QVariant &participantInfo, participantsInfo) {
162+ QVariantMap newMap = participantInfo.toMap();
163+ newMap[History::FieldParticipantState] = participants.at(count).state();
164+ newMap[History::FieldParticipantRoles] = participants.at(count++).roles();
165+ finalParticipantsList << newMap;
166+ }
167+ result = finalParticipantsList;
168+ } else {
169+ //FIXME: handle contact changes
170+ result = participants.identifiers();
171+ }
172+
173+ break;
174+ }
175+ case ParticipantIdsRole:
176+ result = History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers();
177+ break;
178+ case TimestampRole:
179+ result = QDateTime::fromString(properties[History::FieldTimestamp].toString(), Qt::ISODate);
180+ break;
181+ }
182+
183 return result;
184 }
185
186@@ -187,17 +253,66 @@
187 }
188 }
189
190+QVariantMap HistoryModel::threadForProperties(const QString &accountId, int eventType, const QVariantMap &properties, int matchFlags, bool create)
191+{
192+ QVariantMap newProperties = properties;
193+ if (properties.isEmpty()) {
194+ return QVariantMap();
195+ }
196+
197+ if (newProperties.contains(History::FieldParticipantIds)) {
198+ newProperties[History::FieldParticipantIds] = newProperties[History::FieldParticipantIds].toStringList();
199+ }
200+
201+ History::Thread thread = History::Manager::instance()->threadForProperties(accountId,
202+ (History::EventType)eventType,
203+ newProperties,
204+ (History::MatchFlags)matchFlags,
205+ create);
206+ if (!thread.isNull()) {
207+ return thread.properties();
208+ }
209+
210+ return QVariantMap();
211+}
212+
213+QString HistoryModel::threadIdForProperties(const QString &accountId, int eventType, const QVariantMap &properties, int matchFlags, bool create)
214+{
215+ QVariantMap newProperties = properties;
216+ if (properties.isEmpty()) {
217+ return QString::null;
218+ }
219+
220+ if (newProperties.contains(History::FieldParticipantIds)) {
221+ newProperties[History::FieldParticipantIds] = newProperties[History::FieldParticipantIds].toStringList();
222+ }
223+
224+ History::Thread thread = History::Manager::instance()->threadForProperties(accountId,
225+ (History::EventType)eventType,
226+ newProperties,
227+ (History::MatchFlags)matchFlags,
228+ create);
229+ if (!thread.isNull()) {
230+ return thread.threadId();
231+ }
232+
233+ return QString::null;
234+}
235+
236 QVariantMap HistoryModel::threadForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
237 {
238 if (participants.isEmpty()) {
239 return QVariantMap();
240 }
241
242- History::Thread thread = History::Manager::instance()->threadForParticipants(accountId,
243- (History::EventType)eventType,
244- participants,
245- (History::MatchFlags)matchFlags,
246- create);
247+ QVariantMap properties;
248+ properties[History::FieldParticipantIds] = participants;
249+
250+ History::Thread thread = History::Manager::instance()->threadForProperties(accountId,
251+ (History::EventType)eventType,
252+ properties,
253+ (History::MatchFlags)matchFlags,
254+ create);
255 if (!thread.isNull()) {
256 return thread.properties();
257 }
258@@ -211,11 +326,14 @@
259 return QString::null;
260 }
261
262- History::Thread thread = History::Manager::instance()->threadForParticipants(accountId,
263- (History::EventType)eventType,
264- participants,
265- (History::MatchFlags)matchFlags,
266- create);
267+ QVariantMap properties;
268+ properties[History::FieldParticipantIds] = participants;
269+
270+ History::Thread thread = History::Manager::instance()->threadForProperties(accountId,
271+ (History::EventType)eventType,
272+ properties,
273+ (History::MatchFlags)matchFlags,
274+ create);
275 if (!thread.isNull()) {
276 return thread.threadId();
277 }
278@@ -223,7 +341,7 @@
279 return QString::null;
280 }
281
282-bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message)
283+bool HistoryModel::writeTextInformationEvent(const QString &accountId, const QString &threadId, const QStringList &participants, const QString &message, int informationType, const QString &subject)
284 {
285 if (participants.isEmpty() || threadId.isEmpty() || accountId.isEmpty()) {
286 return false;
287@@ -232,7 +350,7 @@
288 History::TextEvent historyEvent = History::TextEvent(accountId,
289 threadId,
290 QString(QCryptographicHash::hash(QByteArray(
291- QDateTime::currentDateTime().toString().toLatin1()),
292+ QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz").toLatin1()),
293 QCryptographicHash::Md5).toHex()),
294 "self",
295 QDateTime::currentDateTime(),
296@@ -240,7 +358,9 @@
297 message,
298 History::MessageTypeInformation,
299 History::MessageStatusUnknown,
300- QDateTime::currentDateTime());
301+ QDateTime::currentDateTime(),
302+ subject,
303+ (History::InformationType)informationType);
304 History::Events events;
305 events << historyEvent;
306 return History::Manager::instance()->writeEvents(events);
307
308=== modified file 'Ubuntu/History/historymodel.h'
309--- Ubuntu/History/historymodel.h 2015-10-08 21:47:42 +0000
310+++ Ubuntu/History/historymodel.h 2016-11-24 12:52:24 +0000
311@@ -1,5 +1,5 @@
312 /*
313- * Copyright (C) 2013-2014 Canonical, Ltd.
314+ * Copyright (C) 2013-2016 Canonical, Ltd.
315 *
316 * Authors:
317 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
318@@ -39,14 +39,22 @@
319 Q_PROPERTY(EventType type READ type WRITE setType NOTIFY typeChanged)
320 Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
321 Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
322+ Q_ENUMS(ChatType)
323 Q_ENUMS(EventType)
324 Q_ENUMS(MessageType)
325 Q_ENUMS(MatchFlag)
326 Q_ENUMS(MessageStatus)
327 Q_ENUMS(AttachmentFlag)
328 Q_ENUMS(Role)
329+ Q_ENUMS(InformationType)
330
331 public:
332+ enum ChatType {
333+ ChatTypeNone = History::ChatTypeNone,
334+ ChatTypeContact = History::ChatTypeContact,
335+ ChatTypeRoom = History::ChatTypeRoom
336+ };
337+
338 enum EventType {
339 EventTypeText = History::EventTypeText,
340 EventTypeVoice = History::EventTypeVoice
341@@ -83,12 +91,36 @@
342 AttachmentPending = History::AttachmentPending,
343 AttachmentError = History::AttachmentError
344 };
345+
346+ enum InformationType
347+ {
348+ InformationTypeNone = History::InformationTypeNone,
349+ InformationTypeSimChange = History::InformationTypeSimChange,
350+ InformationTypeText = History::InformationTypeText,
351+ InformationTypeSelfJoined = History::InformationTypeSelfJoined,
352+ InformationTypeJoined = History::InformationTypeJoined,
353+ InformationTypeTitleChanged = History::InformationTypeTitleChanged,
354+ InformationTypeInvitationSent = History::InformationTypeInvitationSent,
355+ InformationTypeLeaving = History::InformationTypeLeaving,
356+ InformationTypeSelfLeaving = History::InformationTypeSelfLeaving,
357+ InformationTypeAdminGranted = History::InformationTypeAdminGranted,
358+ InformationTypeAdminRemoved = History::InformationTypeAdminRemoved,
359+ InformationTypeSelfAdminGranted = History::InformationTypeSelfAdminGranted,
360+ InformationTypeSelfAdminRemoved = History::InformationTypeSelfAdminRemoved,
361+ InformationTypeSelfKicked = History::InformationTypeSelfKicked,
362+ InformationTypeGroupGone = History::InformationTypeGroupGone
363+ };
364+
365
366 enum Role {
367 AccountIdRole = Qt::UserRole,
368 ThreadIdRole,
369 ParticipantsRole,
370+ ParticipantsLocalPendingRole,
371+ ParticipantsRemotePendingRole,
372+ ParticipantIdsRole,
373 TypeRole,
374+ TimestampRole,
375 PropertiesRole,
376 LastRole
377 };
378@@ -112,6 +144,18 @@
379 bool matchContacts() const;
380 void setMatchContacts(bool value);
381
382+ Q_INVOKABLE QVariantMap threadForProperties(const QString &accountId,
383+ int eventType,
384+ const QVariantMap &properties,
385+ int matchFlags = (int)History::MatchCaseSensitive,
386+ bool create = false);
387+
388+ Q_INVOKABLE QString threadIdForProperties(const QString &accountId,
389+ int eventType,
390+ const QVariantMap &properties,
391+ int matchFlags = (int)History::MatchCaseSensitive,
392+ bool create = false);
393+
394 Q_INVOKABLE QVariantMap threadForParticipants(const QString &accountId,
395 int eventType,
396 const QStringList &participants,
397@@ -125,7 +169,9 @@
398 Q_INVOKABLE bool writeTextInformationEvent(const QString &accountId,
399 const QString &threadId,
400 const QStringList &participants,
401- const QString &message);
402+ const QString &message,
403+ int informationType = (int)History::InformationTypeNone,
404+ const QString &subject = QString());
405
406 Q_INVOKABLE virtual QVariant get(int row) const;
407
408
409=== modified file 'Ubuntu/History/historythreadmodel.cpp'
410--- Ubuntu/History/historythreadmodel.cpp 2015-10-08 19:35:40 +0000
411+++ Ubuntu/History/historythreadmodel.cpp 2016-11-24 12:52:24 +0000
412@@ -1,5 +1,5 @@
413 /*
414- * Copyright (C) 2013-2015 Canonical, Ltd.
415+ * Copyright (C) 2013-2016 Canonical, Ltd.
416 *
417 * Authors:
418 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
419@@ -38,6 +38,8 @@
420 mRoles = HistoryModel::roleNames();
421 mRoles[CountRole] = "count";
422 mRoles[UnreadCountRole] = "unreadCount";
423+ mRoles[ChatType] = "chatType";
424+ mRoles[ChatRoomInfo] = "chatRoomInfo";
425
426 // roles related to the thread´s last event
427 mRoles[LastEventIdRole] = "eventId";
428@@ -104,6 +106,12 @@
429 case UnreadCountRole:
430 result = thread.unreadCount();
431 break;
432+ case ChatType:
433+ result = thread.chatType();
434+ break;
435+ case ChatRoomInfo:
436+ result = thread.chatRoomInfo();
437+ break;
438 case PropertiesRole:
439 result = thread.properties();
440 break;
441
442=== modified file 'Ubuntu/History/historythreadmodel.h'
443--- Ubuntu/History/historythreadmodel.h 2015-09-29 14:28:17 +0000
444+++ Ubuntu/History/historythreadmodel.h 2016-11-24 12:52:24 +0000
445@@ -1,5 +1,5 @@
446 /*
447- * Copyright (C) 2013-2015 Canonical, Ltd.
448+ * Copyright (C) 2013-2016 Canonical, Ltd.
449 *
450 * Authors:
451 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
452@@ -40,6 +40,8 @@
453 enum ThreadRole {
454 CountRole = HistoryModel::LastRole,
455 UnreadCountRole,
456+ ChatType,
457+ ChatRoomInfo,
458 LastEventIdRole,
459 LastEventSenderIdRole,
460 LastEventTimestampRole,
461
462=== modified file 'daemon/CMakeLists.txt'
463--- daemon/CMakeLists.txt 2015-09-30 13:17:52 +0000
464+++ daemon/CMakeLists.txt 2016-11-24 12:52:24 +0000
465@@ -4,6 +4,7 @@
466 historydaemon.cpp
467 historyservicedbus.cpp
468 pluginmanager.cpp
469+ rolesinterface.cpp
470 textchannelobserver.cpp
471 )
472
473
474=== modified file 'daemon/HistoryService.xml'
475--- daemon/HistoryService.xml 2015-09-23 15:08:07 +0000
476+++ daemon/HistoryService.xml 2016-11-24 12:52:24 +0000
477@@ -9,6 +9,20 @@
478 <dox:d>
479 An interface to the history service
480 </dox:d>
481+ <method name="ThreadForProperties">
482+ <dox:d><![CDATA[
483+ Return an existing thread for the given parameters.
484+ ]]></dox:d>
485+ <arg name="accountId" type="s" direction="in"/>
486+ <arg name="type" type="i" direction="in"/>
487+ <arg name="properties" type="a{sv}" direction="in"/>
488+ <arg name="matchFlags" type="i" direction="in"/>
489+ <arg name="create" type="b" direction="in"/>
490+ <arg type="a{sv}" direction="out"/>
491+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
492+ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
493+ </method>
494+
495 <method name="ThreadForParticipants">
496 <dox:d><![CDATA[
497 Return an existing thread for the given parameters.
498
499=== modified file 'daemon/historydaemon.cpp'
500--- daemon/historydaemon.cpp 2015-11-20 12:53:49 +0000
501+++ daemon/historydaemon.cpp 2016-11-24 12:52:24 +0000
502@@ -1,5 +1,5 @@
503 /*
504- * Copyright (C) 2013-2015 Canonical, Ltd.
505+ * Copyright (C) 2013-2016 Canonical, Ltd.
506 *
507 * Authors:
508 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
509@@ -23,20 +23,97 @@
510 #include "telepathyhelper_p.h"
511 #include "filter.h"
512 #include "sort.h"
513+#include "utils_p.h"
514
515 #include "pluginmanager.h"
516 #include "plugin.h"
517 #include "pluginthreadview.h"
518 #include "plugineventview.h"
519+#include "textevent.h"
520
521 #include <QStandardPaths>
522 #include <QCryptographicHash>
523 #include <TelepathyQt/CallChannel>
524+#include <TelepathyQt/PendingVariantMap>
525 #include <TelepathyQt/ReferencedHandles>
526
527+#include <TelepathyQt/PendingVariant>
528+#include <TelepathyQt/PendingOperation>
529+
530+Q_DECLARE_METATYPE(RolesMap)
531+
532+const constexpr static int AdminRole = 2;
533+
534+enum ChannelGroupChangeReason
535+{
536+ ChannelGroupChangeReasonNone = 0,
537+ ChannelGroupChangeReasonOffline = 1,
538+ ChannelGroupChangeReasonKicked = 2,
539+ ChannelGroupChangeReasonBusy = 3,
540+ ChannelGroupChangeReasonInvited = 4,
541+ ChannelGroupChangeReasonBanned = 5,
542+ ChannelGroupChangeReasonError = 6,
543+ ChannelGroupChangeReasonInvalidContact = 7,
544+ ChannelGroupChangeReasonNoAnswer = 8,
545+ ChannelGroupChangeReasonRenamed = 9,
546+ ChannelGroupChangeReasonPermissionDenied = 10,
547+ ChannelGroupChangeReasonSeparated = 11,
548+
549+ // additional enum values not included in original ChannelGroupChangeReason
550+ // telepathy enumeration but needed here to provide extra info to client when group
551+ // is cancelled
552+ ChannelGroupChangeReasonGone = 12,
553+ ChannelGroupChangeReasonRejected = 13
554+};
555+
556+const QDBusArgument &operator>>(const QDBusArgument &argument, RolesMap &roles)
557+{
558+ argument.beginMap();
559+ while ( !argument.atEnd() ) {
560+ argument.beginMapEntry();
561+ uint key,value;
562+ argument >> key >> value;
563+ argument.endMapEntry();
564+ roles[key] = value;
565+ }
566+
567+ argument.endMap();
568+ return argument;
569+}
570+
571+bool foundAsMemberInThread(const Tp::ContactPtr& contact, QVariantMap thread)
572+{
573+ Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
574+ // found if same identifier and as member into thread info
575+ if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
576+ contact->id(),
577+ participant.toMap()[History::FieldIdentifier].toString()) &&
578+ participant.toMap()[History::FieldParticipantState].toUInt() == History::ParticipantStateRegular)
579+ {
580+ return true;
581+ }
582+ }
583+ return false;
584+}
585+
586+bool foundInThread(const Tp::ContactPtr& contact, QVariantMap thread)
587+{
588+ Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
589+ if (History::Utils::compareIds(thread[History::FieldAccountId].toString(),
590+ contact->id(),
591+ participant.toMap()[History::FieldIdentifier].toString()))
592+ {
593+ return true;
594+ }
595+ }
596+ return false;
597+}
598+
599 HistoryDaemon::HistoryDaemon(QObject *parent)
600 : QObject(parent), mCallObserver(this), mTextObserver(this)
601 {
602+ qRegisterMetaType<HandleRolesMap>();
603+ qDBusRegisterMetaType<HandleRolesMap>();
604 // get the first plugin
605 if (!History::PluginManager::instance()->plugins().isEmpty()) {
606 mBackend = History::PluginManager::instance()->plugins().first();
607@@ -65,6 +142,9 @@
608 connect(&mTextObserver,
609 SIGNAL(messageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)),
610 SLOT(onMessageRead(Tp::TextChannelPtr,Tp::ReceivedMessage)));
611+ connect(&mTextObserver,
612+ SIGNAL(channelAvailable(Tp::TextChannelPtr)),
613+ SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
614
615 // FIXME: we need to do this in a better way, but for now this should do
616 mProtocolFlags["ofono"] = History::MatchPhoneNumber;
617@@ -81,37 +161,153 @@
618 return self;
619 }
620
621-QStringList HistoryDaemon::participantsFromChannel(const Tp::TextChannelPtr &textChannel)
622-{
623- QStringList participants;
624+void HistoryDaemon::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
625+{
626+ Q_UNUSED(added);
627+ Q_UNUSED(removed);
628+
629+ ChannelInterfaceRolesInterface *roles_interface = qobject_cast<ChannelInterfaceRolesInterface*>(sender());
630+ RolesMap roles = roles_interface->getRoles();
631+
632+ Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()->parent()));
633+ QVariantMap properties = propertiesFromChannel(channel);
634+ QVariantMap thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
635+ History::EventTypeText,
636+ properties,
637+ matchFlagsForChannel(channel),
638+ false);
639+
640+ writeRolesInformationEvents(thread, channel, roles);
641+
642+ updateRoomRoles(channel, roles);
643+}
644+
645+QVariantMap HistoryDaemon::propertiesFromChannel(const Tp::ChannelPtr &textChannel)
646+{
647+ QVariantMap properties;
648+ QVariantList participants;
649+ QStringList participantIds;
650+
651+ ChannelInterfaceRolesInterface *roles_interface = textChannel->optionalInterface<ChannelInterfaceRolesInterface>();
652+ RolesMap roles;
653+ if (roles_interface) {
654+ roles = roles_interface->getRoles();
655+ }
656+
657 Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupContacts(false)) {
658- participants << contact->id();
659+ QVariantMap contactProperties;
660+ contactProperties[History::FieldAlias] = contact->alias();
661+ contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
662+ contactProperties[History::FieldIdentifier] = contact->id();
663+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
664+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
665+ participantIds << contact->id();
666+ participants << contactProperties;
667+ }
668+
669+ Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupRemotePendingContacts(false)) {
670+ QVariantMap contactProperties;
671+ contactProperties[History::FieldAlias] = contact->alias();
672+ contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
673+ contactProperties[History::FieldIdentifier] = contact->id();
674+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRemotePending;
675+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
676+ participantIds << contact->id();
677+ participants << contactProperties;
678+ }
679+
680+ Q_FOREACH(const Tp::ContactPtr contact, textChannel->groupLocalPendingContacts(false)) {
681+ QVariantMap contactProperties;
682+ contactProperties[History::FieldAlias] = contact->alias();
683+ contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
684+ contactProperties[History::FieldIdentifier] = contact->id();
685+ contactProperties[History::FieldParticipantState] = History::ParticipantStateLocalPending;
686+ contactProperties[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
687+ participantIds << contact->id();
688+ participants << contactProperties;
689 }
690
691 if (participants.isEmpty() && textChannel->targetHandleType() == Tp::HandleTypeContact &&
692- textChannel->targetContact() == textChannel->connection()->selfContact()) {
693- participants << textChannel->targetContact()->id();
694- }
695- return participants;
696+ textChannel->targetContact() == textChannel->connection()->selfContact()) {
697+ QVariantMap contactProperties;
698+ contactProperties[History::FieldAlias] = textChannel->targetContact()->alias();
699+ contactProperties[History::FieldAccountId] = textChannel->property(History::FieldAccountId).toString();
700+ contactProperties[History::FieldIdentifier] = textChannel->targetContact()->id();
701+ contactProperties[History::FieldParticipantState] = History::ParticipantStateRegular;
702+ participantIds << textChannel->targetContact()->id();
703+ participants << contactProperties;
704+ }
705+
706+ // We map chatType directly from telepathy targetHandleType: None, Contact, Room
707+ properties[History::FieldChatType] = textChannel->targetHandleType();
708+ properties[History::FieldParticipants] = participants;
709+ properties[History::FieldParticipantIds] = participantIds;
710+
711+ QVariantMap roomProperties;
712+ switch(textChannel->targetHandleType()) {
713+ case Tp::HandleTypeRoom:
714+ if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM)) {
715+ auto room_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
716+ QVariantMap map = getInterfaceProperties(room_interface);
717+ for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
718+ if (iter.value().isValid()) {
719+ roomProperties[iter.key()] = iter.value();
720+ }
721+ }
722+ }
723+ if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM_CONFIG)) {
724+ auto room_config_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceRoomConfigInterface>();
725+ QVariantMap map = getInterfaceProperties(room_config_interface);
726+ for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
727+ if (iter.value().isValid()) {
728+ roomProperties[iter.key()] = iter.value();
729+ }
730+ }
731+ }
732+ if (textChannel->hasInterface(TP_QT_IFACE_CHANNEL_INTERFACE_SUBJECT)) {
733+ auto subject_interface = textChannel->optionalInterface<Tp::Client::ChannelInterfaceSubjectInterface>();
734+ QVariantMap map = getInterfaceProperties(subject_interface);
735+ for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
736+ if (iter.value().isValid()) {
737+ roomProperties[iter.key()] = iter.value();
738+ }
739+ }
740+ }
741+
742+ properties[History::FieldChatRoomInfo] = roomProperties;
743+ properties[History::FieldThreadId] = textChannel->targetId();
744+ break;
745+ case Tp::HandleTypeContact:
746+ case Tp::HandleTypeNone:
747+ default:
748+ break;
749+ }
750+
751+ return properties;
752 }
753
754-QVariantMap HistoryDaemon::threadForParticipants(const QString &accountId,
755- History::EventType type,
756- const QStringList &participants,
757- History::MatchFlags matchFlags,
758- bool create)
759+QVariantMap HistoryDaemon::threadForProperties(const QString &accountId,
760+ History::EventType type,
761+ const QVariantMap &properties,
762+ History::MatchFlags matchFlags,
763+ bool create)
764 {
765 if (!mBackend) {
766 return QVariantMap();
767 }
768
769- QVariantMap thread = mBackend->threadForParticipants(accountId,
770- type,
771- participants,
772- matchFlags);
773+ QVariantMap thread = mBackend->threadForProperties(accountId,
774+ type,
775+ properties,
776+ matchFlags);
777 if (thread.isEmpty() && create) {
778- thread = mBackend->createThreadForParticipants(accountId, type, participants);
779+ thread = mBackend->createThreadForProperties(accountId, type, properties);
780 if (!thread.isEmpty()) {
781+ if (properties.contains("Requested") && properties[History::FieldChatType].toInt() == History::ChatTypeRoom) {
782+ QVariantMap map = thread[History::FieldChatRoomInfo].toMap();
783+ map["Requested"] = properties["Requested"];
784+ thread[History::FieldChatRoomInfo] = map;
785+ }
786 mDBus.notifyThreadsAdded(QList<QVariantMap>() << thread);
787 }
788 }
789@@ -174,7 +370,7 @@
790 return mBackend->getSingleEvent((History::EventType)type, accountId, threadId, eventId);
791 }
792
793-bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events)
794+bool HistoryDaemon::writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties)
795 {
796 if (!mBackend) {
797 return false;
798@@ -206,7 +402,7 @@
799 }
800
801 // only get the thread AFTER the event is written to make sure it is up-to-date
802- QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
803+ QVariantMap thread = getSingleThread(type, accountId, threadId, properties);
804 QString hash = hashThread(thread);
805 threads[hash] = thread;
806
807@@ -245,7 +441,6 @@
808
809 bool HistoryDaemon::removeEvents(const QList<QVariantMap> &events)
810 {
811- qDebug() << __PRETTY_FUNCTION__;
812 if (!mBackend) {
813 return false;
814 }
815@@ -319,7 +514,6 @@
816
817 bool HistoryDaemon::removeThreads(const QList<QVariantMap> &threads)
818 {
819- qDebug() << __PRETTY_FUNCTION__;
820 if (!mBackend) {
821 return false;
822 }
823@@ -360,7 +554,6 @@
824
825 void HistoryDaemon::onObserverCreated()
826 {
827- qDebug() << __PRETTY_FUNCTION__;
828 History::ChannelObserver *observer = History::TelepathyHelper::instance()->channelObserver();
829
830 connect(observer, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
831@@ -371,10 +564,14 @@
832
833 void HistoryDaemon::onCallEnded(const Tp::CallChannelPtr &channel)
834 {
835- qDebug() << __PRETTY_FUNCTION__;
836- QStringList participants;
837+ QVariantMap properties = propertiesFromChannel(channel);
838+ QVariantList participants;
839 Q_FOREACH(const Tp::ContactPtr contact, channel->remoteMembers()) {
840- participants << contact->id();
841+ QVariantMap contactProperties;
842+ contactProperties[History::FieldAlias] = contact->alias();
843+ contactProperties[History::FieldIdentifier] = contact->id();
844+ contactProperties[History::FieldAccountId] = channel->property(History::FieldAccountId).toString();
845+ participants << contactProperties;
846 }
847
848 // it shouldn't happen, but in case it does, we won't crash
849@@ -384,11 +581,11 @@
850 }
851
852 QString accountId = channel->property(History::FieldAccountId).toString();
853- QVariantMap thread = threadForParticipants(accountId,
854- History::EventTypeVoice,
855- participants,
856- matchFlagsForChannel(channel),
857- true);
858+ QVariantMap thread = threadForProperties(accountId,
859+ History::EventTypeVoice,
860+ properties,
861+ matchFlagsForChannel(channel),
862+ true);
863 // fill the call info
864 QDateTime timestamp = channel->property(History::FieldTimestamp).toDateTime();
865
866@@ -414,18 +611,284 @@
867 event[History::FieldMissed] = missed;
868 event[History::FieldDuration] = duration;
869 // FIXME: check what to do when there are more than just one remote participant
870- event[History::FieldRemoteParticipant] = participants[0];
871- writeEvents(QList<QVariantMap>() << event);
872+ event[History::FieldRemoteParticipant] = participants[0].toMap()[History::FieldIdentifier];
873+ writeEvents(QList<QVariantMap>() << event, properties);
874+}
875+
876+void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
877+{
878+ // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
879+ // before they receive any message.
880+ // for other types, we can wait until messages are received
881+ if (channel->targetHandleType() == Tp::HandleTypeRoom) {
882+ QString accountId = channel->property(History::FieldAccountId).toString();
883+ QVariantMap properties = propertiesFromChannel(channel);
884+
885+ // first try to fetch the existing thread to see if there is any.
886+ QVariantMap thread = threadForProperties(accountId,
887+ History::EventTypeText,
888+ properties,
889+ matchFlagsForChannel(channel),
890+ false);
891+ if (thread.isEmpty()) {
892+ // if there no existing thread, create one
893+ properties["Requested"] = channel->isRequested();
894+ thread = threadForProperties(accountId,
895+ History::EventTypeText,
896+ properties,
897+ matchFlagsForChannel(channel),
898+ true);
899+
900+ // write information event including all initial invitees
901+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
902+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
903+ }
904+
905+ // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
906+ // for reflect in conversation information events for modified participants.
907+ updateRoomParticipants(channel);
908+ }
909+
910+ // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
911+ if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
912+ // only write self joined notification if protocol is not a phone one.
913+ // FIXME (rmescandon): as a first solution, let's take only ofono as phone protocol
914+ if (History::TelepathyHelper::instance()->accountForId(accountId)->protocolName() != "ofono") {
915+ writeInformationEvent(thread, History::InformationTypeSelfJoined);
916+ }
917+ // update backend
918+ updateRoomProperties(channel, QVariantMap{{"Joined", true}});
919+ }
920+
921+ Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
922+ Tp::AbstractInterface *room_config_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomConfigInterface>();
923+ Tp::AbstractInterface *subject_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceSubjectInterface>();
924+ ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
925+
926+ QList<Tp::AbstractInterface*> interfaces;
927+ interfaces << room_interface << room_config_interface << subject_interface << roles_interface;
928+ for (auto interface : interfaces) {
929+ if (interface) {
930+ interface->setMonitorProperties(true);
931+ interface->setProperty(History::FieldAccountId, accountId);
932+ interface->setProperty(History::FieldThreadId, thread[History::FieldThreadId].toString());
933+ interface->setProperty(History::FieldType, thread[History::FieldType].toInt());
934+ connect(interface, SIGNAL(propertiesChanged(const QVariantMap &,const QStringList &)),
935+ SLOT(onRoomPropertiesChanged(const QVariantMap &,const QStringList &)));
936+ // update the stored info
937+ Q_EMIT interface->propertiesChanged(getInterfaceProperties(interface), QStringList());
938+ }
939+ }
940+
941+ connect(channel.data(), SIGNAL(groupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)),
942+ SLOT(onGroupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)));
943+
944+ connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
945+ }
946+}
947+
948+void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
949+ const Tp::Contacts &groupLocalPendingMembersAdded,
950+ const Tp::Contacts &groupRemotePendingMembersAdded,
951+ const Tp::Contacts &groupMembersRemoved,
952+ const Tp::Channel::GroupMemberChangeDetails &details)
953+{
954+ Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()));
955+
956+ QVariantMap properties;
957+ QVariantMap thread;
958+
959+ // information events for members updates.
960+ bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
961+ bool hasMembersAdded = groupMembersAdded.size() > 0;
962+ bool hasMembersRemoved = groupMembersRemoved.size() > 0;
963+
964+ if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
965+ properties = propertiesFromChannel(channel);
966+ thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
967+ History::EventTypeText,
968+ properties,
969+ matchFlagsForChannel(channel),
970+ false);
971+ if (!thread.isEmpty()) {
972+ if (hasRemotePendingMembersAdded) {
973+ Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
974+ if (!foundInThread(contact, thread)) {
975+ writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
976+ }
977+ }
978+ }
979+ if (hasMembersAdded) {
980+ Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
981+ // if this member was not previously regular member in thread, notify about his join
982+ if (!foundAsMemberInThread(contact, thread)) {
983+ writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());
984+ }
985+ }
986+ }
987+
988+ if (hasMembersRemoved) {
989+ if (channel->groupSelfContactRemoveInfo().isValid()) {
990+ // evaluate if we are leaving by our own or we are kicked
991+ History::InformationType type = History::InformationTypeSelfLeaving;
992+ if (channel->groupSelfContactRemoveInfo().hasReason()) {
993+ switch (channel->groupSelfContactRemoveInfo().reason()) {
994+ case ChannelGroupChangeReasonKicked:
995+ type = History::InformationTypeSelfKicked;
996+ break;
997+ case ChannelGroupChangeReasonGone:
998+ type = History::InformationTypeGroupGone;
999+ break;
1000+ }
1001+ }
1002+ writeInformationEvent(thread, type);
1003+ // update backend
1004+ updateRoomProperties(channel, QVariantMap{{"Joined", false}});
1005+ }
1006+ else // don't notify any other group member removal if we are leaving the group
1007+ {
1008+ Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
1009+ // inform about removed members other than us
1010+ if (contact->id() != channel->groupSelfContact()->id()) {
1011+ writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
1012+ }
1013+ }
1014+ }
1015+ }
1016+ }
1017+ }
1018+
1019+ updateRoomParticipants(channel);
1020+}
1021+
1022+void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
1023+{
1024+ if (!channel) {
1025+ return;
1026+ }
1027+
1028+ QVariantList participants;
1029+ QStringList contactsAdded;
1030+
1031+ ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
1032+ RolesMap roles;
1033+ if (roles_interface) {
1034+ roles = roles_interface->getRoles();
1035+ }
1036+
1037+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
1038+ QVariantMap participant;
1039+ contactsAdded << contact->id();
1040+ participant[History::FieldIdentifier] = contact->id();
1041+ participant[History::FieldAlias] = contact->alias();
1042+ participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
1043+ participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
1044+ participants << QVariant::fromValue(participant);
1045+ }
1046+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
1047+ QVariantMap participant;
1048+ contactsAdded << contact->id();
1049+ participant[History::FieldIdentifier] = contact->id();
1050+ participant[History::FieldAlias] = contact->alias();
1051+ participant[History::FieldParticipantState] = History::ParticipantStateLocalPending;
1052+ participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
1053+ participants << QVariant::fromValue(participant);
1054+ }
1055+
1056+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
1057+ // do not include remote and local pending members
1058+ if (contactsAdded.contains(contact->id())) {
1059+ continue;
1060+ }
1061+ QVariantMap participant;
1062+ participant[History::FieldIdentifier] = contact->id();
1063+ participant[History::FieldAlias] = contact->alias();
1064+ participant[History::FieldParticipantState] = History::ParticipantStateRegular;
1065+ participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
1066+ participants << QVariant::fromValue(participant);
1067+ }
1068+
1069+ QString accountId = channel->property(History::FieldAccountId).toString();
1070+ QString threadId = channel->targetId();
1071+ if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
1072+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1073+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1074+ }
1075+}
1076+
1077+void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
1078+{
1079+ if (!channel) {
1080+ return;
1081+ }
1082+
1083+ QVariantMap participantsRoles;
1084+
1085+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
1086+ participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
1087+ }
1088+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
1089+ participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
1090+ }
1091+
1092+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
1093+ if (!participantsRoles.contains(contact->id())) {
1094+ participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
1095+ }
1096+ }
1097+
1098+ // update participants roles
1099+ QString accountId = channel->property(History::FieldAccountId).toString();
1100+ QString threadId = channel->targetId();
1101+ if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
1102+ QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
1103+ mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
1104+ }
1105+
1106+ // update self roles in room properties
1107+ uint selfRoles = rolesMap[channel->groupSelfContact()->handle().at(0)];
1108+ updateRoomProperties(channel, QVariantMap{{"SelfRoles", selfRoles}});
1109+}
1110+
1111+void HistoryDaemon::onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated)
1112+{
1113+ QString accountId = sender()->property(History::FieldAccountId).toString();
1114+ QString threadId = sender()->property(History::FieldThreadId).toString();
1115+ History::EventType type = (History::EventType)sender()->property(History::FieldType).toInt();
1116+
1117+ // get thread before updating to see if there are changes to insert as information events
1118+ QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
1119+ if (!thread.empty()) {
1120+ writeRoomChangesInformationEvents(thread, properties);
1121+ }
1122+
1123+ updateRoomProperties(accountId, threadId, type, properties, invalidated);
1124+}
1125+
1126+void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
1127+{
1128+ QString accountId = channel->property(History::FieldAccountId).toString();
1129+ QString threadId = channel->targetId();
1130+ History::EventType type = History::EventTypeText;
1131+ updateRoomProperties(accountId, threadId, type, properties, QStringList());
1132+}
1133+
1134+void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
1135+{
1136+ if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
1137+ QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
1138+ mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
1139+ }
1140 }
1141
1142 void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
1143 {
1144- qDebug() << __PRETTY_FUNCTION__;
1145 QString eventId;
1146 Tp::MessagePart header = message.header();
1147 QString senderId;
1148+ QVariantMap properties = propertiesFromChannel(textChannel);
1149 History::MessageStatus status = History::MessageStatusUnknown;
1150- if (message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
1151+ if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
1152 senderId = "self";
1153 status = History::MessageStatusDelivered;
1154 } else {
1155@@ -452,6 +915,13 @@
1156 return;
1157 }
1158
1159+ // FIXME: if this message is already read, don't allow reverting the status.
1160+ // we need to check if this is the right place to do it.
1161+ if (textEvent[History::FieldMessageStatus].toInt() == History::MessageStatusRead) {
1162+ qWarning() << "Skipping delivery report as it is trying to revert the Read status of an existing message to the following status:" << message.deliveryDetails().status();
1163+ return;
1164+ }
1165+
1166 History::MessageStatus status;
1167 switch (message.deliveryDetails().status()) {
1168 case Tp::DeliveryStatusAccepted:
1169@@ -478,20 +948,18 @@
1170 }
1171
1172 textEvent[History::FieldMessageStatus] = (int) status;
1173- if (!writeEvents(QList<QVariantMap>() << textEvent)) {
1174+ if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
1175 qWarning() << "Failed to save the new message status!";
1176 }
1177
1178 return;
1179 }
1180
1181- QStringList participants = participantsFromChannel(textChannel);
1182-
1183- QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
1184- History::EventTypeText,
1185- participants,
1186- matchFlagsForChannel(textChannel),
1187- true);
1188+ QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
1189+ History::EventTypeText,
1190+ properties,
1191+ matchFlagsForChannel(textChannel),
1192+ true);
1193 int count = 1;
1194 QList<QVariantMap> attachments;
1195 History::MessageType type = History::MessageTypeText;
1196@@ -558,16 +1026,22 @@
1197 event[History::FieldSubject] = subject;
1198 event[History::FieldAttachments] = QVariant::fromValue(attachments);
1199
1200- writeEvents(QList<QVariantMap>() << event);
1201+ writeEvents(QList<QVariantMap>() << event, properties);
1202+
1203+ // if this messages supersedes another one, remove the original message
1204+ if (!message.supersededToken().isEmpty()) {
1205+ event[History::FieldEventId] = message.supersededToken();
1206+ removeEvents(QList<QVariantMap>() << event);
1207+ }
1208 }
1209
1210 QVariantMap HistoryDaemon::getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId)
1211 {
1212- QStringList participants = participantsFromChannel(textChannel);
1213+ QVariantMap properties = propertiesFromChannel(textChannel);
1214
1215- QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
1216+ QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
1217 History::EventTypeText,
1218- participants,
1219+ properties,
1220 matchFlagsForChannel(textChannel),
1221 false);
1222 if (thread.isEmpty()) {
1223@@ -587,6 +1061,7 @@
1224 void HistoryDaemon::onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
1225 {
1226 QVariantMap textEvent = getSingleEventFromTextChannel(textChannel, message.messageToken());
1227+ QVariantMap properties = propertiesFromChannel(textChannel);
1228
1229 if (textEvent.isEmpty()) {
1230 qWarning() << "Cound not find the original event to update with newEvent = false.";
1231@@ -594,15 +1069,14 @@
1232 }
1233
1234 textEvent[History::FieldNewEvent] = false;
1235- if (!writeEvents(QList<QVariantMap>() << textEvent)) {
1236+ if (!writeEvents(QList<QVariantMap>() << textEvent, properties)) {
1237 qWarning() << "Failed to save the new message status!";
1238 }
1239 }
1240
1241 void HistoryDaemon::onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken)
1242 {
1243- qDebug() << __PRETTY_FUNCTION__;
1244- QStringList participants = participantsFromChannel(textChannel);
1245+ QVariantMap properties = propertiesFromChannel(textChannel);
1246 QList<QVariantMap> attachments;
1247 History::MessageType type = History::MessageTypeText;
1248 int count = 1;
1249@@ -615,9 +1089,9 @@
1250 eventId = messageToken;
1251 }
1252
1253- QVariantMap thread = threadForParticipants(textChannel->property(History::FieldAccountId).toString(),
1254+ QVariantMap thread = threadForProperties(textChannel->property(History::FieldAccountId).toString(),
1255 History::EventTypeText,
1256- participants,
1257+ properties,
1258 matchFlagsForChannel(textChannel),
1259 true);
1260 if (message.hasNonTextContent()) {
1261@@ -666,7 +1140,6 @@
1262 }
1263 }
1264
1265-
1266 QVariantMap event;
1267 event[History::FieldType] = History::EventTypeText;
1268 event[History::FieldAccountId] = thread[History::FieldAccountId];
1269@@ -686,7 +1159,7 @@
1270 event[History::FieldSubject] = "";
1271 event[History::FieldAttachments] = QVariant::fromValue(attachments);
1272
1273- writeEvents(QList<QVariantMap>() << event);
1274+ writeEvents(QList<QVariantMap>() << event, properties);
1275 }
1276
1277 History::MatchFlags HistoryDaemon::matchFlagsForChannel(const Tp::ChannelPtr &channel)
1278@@ -707,3 +1180,88 @@
1279 hash += "#-#" + thread[History::FieldThreadId].toString();
1280 return hash;
1281 }
1282+
1283+QVariantMap HistoryDaemon::getInterfaceProperties(const Tp::AbstractInterface *interface)
1284+{
1285+ QDBusInterface propsInterface(interface->service(), interface->path(), "org.freedesktop.DBus.Properties");
1286+ QDBusReply<QVariantMap> reply = propsInterface.call("GetAll", interface->interface());
1287+ if (!reply.isValid()) {
1288+ qWarning() << "Failed to fetch channel properties for interface" << interface->interface() << reply.error().message();
1289+ }
1290+ return reply.value();
1291+}
1292+
1293+void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
1294+{
1295+ History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
1296+ thread[History::FieldThreadId].toString(),
1297+ QString(QCryptographicHash::hash(QByteArray(
1298+ (QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz") + subject + text).toLatin1()),
1299+ QCryptographicHash::Md5).toHex()),
1300+ sender,
1301+ QDateTime::currentDateTime(),
1302+ false,
1303+ text,
1304+ History::MessageTypeInformation,
1305+ History::MessageStatusUnknown,
1306+ QDateTime::currentDateTime(),
1307+ subject,
1308+ type);
1309+ writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
1310+}
1311+
1312+void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
1313+{
1314+ if (!thread.isEmpty()) {
1315+ // group subject
1316+ QString storedSubject = thread[History::FieldChatRoomInfo].toMap()["Subject"].toString();
1317+ QString newSubject = interfaceProperties["Subject"].toString();
1318+ if (!newSubject.isEmpty() && storedSubject != newSubject) {
1319+ //see if we have an actor. If actor is 'me', we have changed that subject
1320+ QString actor = thread[History::FieldChatRoomInfo].toMap()["Actor"].toString();
1321+ if (actor == "me") {
1322+ actor = "self";
1323+ }
1324+ writeInformationEvent(thread, History::InformationTypeTitleChanged, newSubject, actor);
1325+ }
1326+ }
1327+}
1328+
1329+void HistoryDaemon::writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap)
1330+{
1331+ if (thread.isEmpty()) {
1332+ return;
1333+ }
1334+
1335+ if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
1336+ return;
1337+ }
1338+
1339+ // list of identifiers for current channel admins
1340+ QStringList adminIds;
1341+
1342+ Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
1343+ // see if admin role (ChannelAdminRole == 2)
1344+ if (rolesMap[contact->handle().at(0)] & AdminRole) {
1345+ adminIds << contact->id();
1346+ }
1347+ }
1348+
1349+ Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
1350+ QString participantId = participant.toMap()[History::FieldIdentifier].toString();
1351+ if (adminIds.contains(participantId)) {
1352+ // see if already was admin or not (ChannelAdminRole == 2)
1353+ if (! (participant.toMap()[History::FieldParticipantRoles].toUInt() & AdminRole)) {
1354+ writeInformationEvent(thread, History::InformationTypeAdminGranted, participantId);
1355+ }
1356+ }
1357+ }
1358+
1359+ //evaluate now self roles
1360+ if (rolesMap[channel->groupSelfContact()->handle().at(0)] & AdminRole) {
1361+ uint selfRoles = thread[History::FieldChatRoomInfo].toMap()["SelfRoles"].toUInt();
1362+ if (! (selfRoles & AdminRole)) {
1363+ writeInformationEvent(thread, History::InformationTypeSelfAdminGranted);
1364+ }
1365+ }
1366+}
1367
1368=== modified file 'daemon/historydaemon.h'
1369--- daemon/historydaemon.h 2015-11-20 11:45:07 +0000
1370+++ daemon/historydaemon.h 2016-11-24 12:52:24 +0000
1371@@ -1,5 +1,5 @@
1372 /*
1373- * Copyright (C) 2013 Canonical, Ltd.
1374+ * Copyright (C) 2013-2016 Canonical, Ltd.
1375 *
1376 * Authors:
1377 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1378@@ -30,6 +30,9 @@
1379 #include "callchannelobserver.h"
1380 #include "historyservicedbus.h"
1381 #include "plugin.h"
1382+#include "rolesinterface.h"
1383+
1384+typedef QMap<uint,uint> RolesMap;
1385
1386 class HistoryDaemon : public QObject
1387 {
1388@@ -39,19 +42,19 @@
1389
1390 static HistoryDaemon *instance();
1391
1392- static QStringList participantsFromChannel(const Tp::TextChannelPtr &textChannel);
1393- QVariantMap threadForParticipants(const QString &accountId,
1394- History::EventType type,
1395- const QStringList &participants,
1396- History::MatchFlags matchFlags = History::MatchCaseSensitive,
1397- bool create = true);
1398+ static QVariantMap propertiesFromChannel(const Tp::ChannelPtr &textChannel);
1399+ QVariantMap threadForProperties(const QString &accountId,
1400+ History::EventType type,
1401+ const QVariantMap &properties,
1402+ History::MatchFlags matchFlags = History::MatchCaseSensitive,
1403+ bool create = true);
1404 QString queryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties);
1405 QString queryEvents(int type, const QVariantMap &sort, const QVariantMap &filter);
1406 QVariantMap getSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties);
1407 QVariantMap getSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId);
1408 QVariantMap getSingleEventFromTextChannel(const Tp::TextChannelPtr textChannel, const QString &messageId);
1409
1410- bool writeEvents(const QList<QVariantMap> &events);
1411+ bool writeEvents(const QList<QVariantMap> &events, const QVariantMap &properties);
1412 bool removeEvents(const QList<QVariantMap> &events);
1413 bool removeThreads(const QList<QVariantMap> &threads);
1414
1415@@ -61,11 +64,27 @@
1416 void onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1417 void onMessageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1418 void onMessageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
1419+ void onTextChannelAvailable(const Tp::TextChannelPtr channel);
1420+ void onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated);
1421+ void onGroupMembersChanged(const Tp::Contacts &groupMembersAdded, const Tp::Contacts &groupLocalPendingMembersAdded,
1422+ const Tp::Contacts &groupRemotePendingMembersAdded, const Tp::Contacts &groupMembersRemoved,
1423+ const Tp::Channel::GroupMemberChangeDetails &details);
1424+ void onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed);
1425
1426 protected:
1427 History::MatchFlags matchFlagsForChannel(const Tp::ChannelPtr &channel);
1428+ void updateRoomParticipants(const Tp::TextChannelPtr channel);
1429+ void updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap);
1430 QString hashThread(const QVariantMap &thread);
1431-
1432+ static QVariantMap getInterfaceProperties(const Tp::AbstractInterface *interface);
1433+ void updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties);
1434+ void updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated);
1435+
1436+ void writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject = QString(), const QString &sender = QString("self"), const QString &text = QString());
1437+
1438+ void writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties);
1439+ void writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
1440+ void writeRolesChangesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap);
1441 private:
1442 HistoryDaemon(QObject *parent = 0);
1443
1444
1445=== modified file 'daemon/historyservicedbus.cpp'
1446--- daemon/historyservicedbus.cpp 2015-10-01 19:44:45 +0000
1447+++ daemon/historyservicedbus.cpp 2016-11-24 12:52:24 +0000
1448@@ -1,5 +1,5 @@
1449 /*
1450- * Copyright (C) 2013 Canonical, Ltd.
1451+ * Copyright (C) 2013-2016 Canonical, Ltd.
1452 *
1453 * Authors:
1454 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1455@@ -34,16 +34,15 @@
1456
1457 bool HistoryServiceDBus::connectToBus()
1458 {
1459- bool ok = QDBusConnection::sessionBus().registerService(History::DBusService);
1460- if (!ok) {
1461- return false;
1462- }
1463-
1464 if (!mAdaptor) {
1465 mAdaptor = new HistoryServiceAdaptor(this);
1466 }
1467
1468- return QDBusConnection::sessionBus().registerObject(History::DBusObjectPath, this);
1469+ if (!QDBusConnection::sessionBus().registerObject(History::DBusObjectPath, this)) {
1470+ return false;
1471+ }
1472+
1473+ return QDBusConnection::sessionBus().registerService(History::DBusService);
1474 }
1475
1476 void HistoryServiceDBus::notifyThreadsAdded(const QList<QVariantMap> &threads)
1477@@ -76,58 +75,67 @@
1478 Q_EMIT EventsRemoved(events);
1479 }
1480
1481+QVariantMap HistoryServiceDBus::ThreadForProperties(const QString &accountId,
1482+ int type,
1483+ const QVariantMap &properties,
1484+ int matchFlags,
1485+ bool create)
1486+{
1487+ return HistoryDaemon::instance()->threadForProperties(accountId,
1488+ (History::EventType) type,
1489+ properties,
1490+ (History::MatchFlags) matchFlags,
1491+ create);
1492+}
1493+
1494 QVariantMap HistoryServiceDBus::ThreadForParticipants(const QString &accountId,
1495 int type,
1496 const QStringList &participants,
1497 int matchFlags,
1498 bool create)
1499 {
1500- return HistoryDaemon::instance()->threadForParticipants(accountId,
1501+ QVariantMap properties;
1502+ properties[History::FieldParticipants] = participants;
1503+
1504+ return HistoryDaemon::instance()->threadForProperties(accountId,
1505 (History::EventType) type,
1506- participants,
1507+ properties,
1508 (History::MatchFlags) matchFlags,
1509 create);
1510 }
1511
1512 bool HistoryServiceDBus::WriteEvents(const QList<QVariantMap> &events)
1513 {
1514- qDebug() << __PRETTY_FUNCTION__;
1515- return HistoryDaemon::instance()->writeEvents(events);
1516+ return HistoryDaemon::instance()->writeEvents(events, QVariantMap());
1517 }
1518
1519 bool HistoryServiceDBus::RemoveThreads(const QList<QVariantMap> &threads)
1520 {
1521- qDebug() << __PRETTY_FUNCTION__;
1522 return HistoryDaemon::instance()->removeThreads(threads);
1523 }
1524
1525 bool HistoryServiceDBus::RemoveEvents(const QList<QVariantMap> &events)
1526 {
1527- qDebug() << __PRETTY_FUNCTION__;
1528 return HistoryDaemon::instance()->removeEvents(events);
1529 }
1530
1531 QString HistoryServiceDBus::QueryThreads(int type, const QVariantMap &sort, const QVariantMap &filter, const QVariantMap &properties)
1532 {
1533- qDebug() << __PRETTY_FUNCTION__;
1534 return HistoryDaemon::instance()->queryThreads(type, sort, filter, properties);
1535 }
1536
1537 QString HistoryServiceDBus::QueryEvents(int type, const QVariantMap &sort, const QVariantMap &filter)
1538 {
1539- qDebug() << __PRETTY_FUNCTION__;
1540 return HistoryDaemon::instance()->queryEvents(type, sort, filter);
1541 }
1542
1543 QVariantMap HistoryServiceDBus::GetSingleThread(int type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
1544 {
1545- qDebug() << __PRETTY_FUNCTION__;
1546 return HistoryDaemon::instance()->getSingleThread(type, accountId, threadId, properties);
1547 }
1548
1549 QVariantMap HistoryServiceDBus::GetSingleEvent(int type, const QString &accountId, const QString &threadId, const QString &eventId)
1550 {
1551- qDebug() << __PRETTY_FUNCTION__;
1552 return HistoryDaemon::instance()->getSingleEvent(type, accountId, threadId, eventId);
1553 }
1554
1555
1556=== modified file 'daemon/historyservicedbus.h'
1557--- daemon/historyservicedbus.h 2015-09-23 15:08:07 +0000
1558+++ daemon/historyservicedbus.h 2016-11-24 12:52:24 +0000
1559@@ -1,5 +1,5 @@
1560 /*
1561- * Copyright (C) 2013 Canonical, Ltd.
1562+ * Copyright (C) 2013-2016 Canonical, Ltd.
1563 *
1564 * Authors:
1565 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1566@@ -50,6 +50,11 @@
1567 const QStringList &participants,
1568 int matchFlags,
1569 bool create);
1570+ QVariantMap ThreadForProperties(const QString &accountId,
1571+ int type,
1572+ const QVariantMap &properties,
1573+ int matchFlags,
1574+ bool create);
1575 bool WriteEvents(const QList <QVariantMap> &events);
1576 bool RemoveThreads(const QList <QVariantMap> &threads);
1577 bool RemoveEvents(const QList <QVariantMap> &events);
1578
1579=== added file 'daemon/rolesinterface.cpp'
1580--- daemon/rolesinterface.cpp 1970-01-01 00:00:00 +0000
1581+++ daemon/rolesinterface.cpp 2016-11-24 12:52:24 +0000
1582@@ -0,0 +1,71 @@
1583+/*
1584+ * Copyright (C) 2016 Canonical, Ltd.
1585+ *
1586+ * Authors:
1587+ * Roberto Mier Escandon <roberto.escandon@canonical.com>
1588+ *
1589+ * This file is part of history-service.
1590+ *
1591+ * history-service is free software; you can redistribute it and/or modify
1592+ * it under the terms of the GNU General Public License as published by
1593+ * the Free Software Foundation; version 3.
1594+ *
1595+ * history-service is distributed in the hope that it will be useful,
1596+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1597+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1598+ * GNU General Public License for more details.
1599+ *
1600+ * You should have received a copy of the GNU General Public License
1601+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1602+ */
1603+
1604+#include <daemon/rolesinterface.h>
1605+
1606+ChannelInterfaceRolesInterface::ChannelInterfaceRolesInterface(const QString& busName, const QString& objectPath, QObject *parent)
1607+ : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), QDBusConnection::sessionBus(), parent)
1608+{
1609+}
1610+
1611+ChannelInterfaceRolesInterface::ChannelInterfaceRolesInterface(const QDBusConnection& connection, const QString& busName, const QString& objectPath, QObject *parent)
1612+ : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), connection, parent)
1613+{
1614+}
1615+
1616+ChannelInterfaceRolesInterface::ChannelInterfaceRolesInterface(Tp::DBusProxy *proxy)
1617+ : Tp::AbstractInterface(proxy, staticInterfaceName())
1618+{
1619+}
1620+
1621+ChannelInterfaceRolesInterface::ChannelInterfaceRolesInterface(const Tp::Client::ChannelInterface& mainInterface)
1622+ : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), mainInterface.parent())
1623+{
1624+}
1625+
1626+ChannelInterfaceRolesInterface::ChannelInterfaceRolesInterface(const Tp::Client::ChannelInterface& mainInterface, QObject *parent)
1627+ : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), parent)
1628+{
1629+}
1630+
1631+void ChannelInterfaceRolesInterface::invalidate(Tp::DBusProxy *proxy,
1632+ const QString &error, const QString &message)
1633+{
1634+ Tp::AbstractInterface::invalidate(proxy, error, message);
1635+}
1636+
1637+HandleRolesMap ChannelInterfaceRolesInterface::getRoles() const
1638+{
1639+ QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(),
1640+ TP_QT_IFACE_PROPERTIES, QLatin1String("Get"));
1641+ msg << interface() << QLatin1String("Roles");
1642+ QDBusMessage result = connection().call(msg);
1643+ return qdbus_cast<HandleRolesMap>(result.arguments().at(0).value<QDBusVariant>().variant());
1644+}
1645+
1646+bool ChannelInterfaceRolesInterface::getCanUpdateRoles() const
1647+{
1648+ QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(),
1649+ TP_QT_IFACE_PROPERTIES, QLatin1String("Get"));
1650+ msg << interface() << QLatin1String("CanUpdateRoles");
1651+ QDBusMessage result = connection().call(msg);
1652+ return qdbus_cast<bool>(result.arguments().at(0).value<QDBusVariant>().variant());
1653+}
1654
1655=== added file 'daemon/rolesinterface.h'
1656--- daemon/rolesinterface.h 1970-01-01 00:00:00 +0000
1657+++ daemon/rolesinterface.h 2016-11-24 12:52:24 +0000
1658@@ -0,0 +1,210 @@
1659+/*
1660+ * Copyright (C) 2016 Canonical, Ltd.
1661+ *
1662+ * Authors:
1663+ * Roberto Mier Escandon <roberto.escandon@canonical.com>
1664+ *
1665+ * This file is part of history-service.
1666+ *
1667+ * history-service is free software; you can redistribute it and/or modify
1668+ * it under the terms of the GNU General Public License as published by
1669+ * the Free Software Foundation; version 3.
1670+ *
1671+ * history-service is distributed in the hope that it will be useful,
1672+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1673+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1674+ * GNU General Public License for more details.
1675+ *
1676+ * You should have received a copy of the GNU General Public License
1677+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1678+ */
1679+
1680+#ifndef CHANNELINTERFACEROLESINTERFACE_H
1681+#define CHANNELINTERFACEROLESINTERFACE_H
1682+
1683+#include <QMap>
1684+
1685+#include <TelepathyQt/AbstractInterface>
1686+#include <TelepathyQt/ChannelInterface>
1687+
1688+/**
1689+ * \struct HandleRolesMap
1690+ * \ingroup mapping
1691+ * \headerfile TelepathyQt/types.h <TelepathyQt/Types>
1692+ *
1693+ * Convertible with
1694+ * QMap<uint, uint>, but needed to have a discrete type in the Qt type system.
1695+ *
1696+ * A map from channel-specific handles to their owners.
1697+ */
1698+struct HandleRolesMap : public QMap<uint, uint>
1699+{
1700+ inline HandleRolesMap() : QMap<uint, uint>() {}
1701+ inline HandleRolesMap(const QMap<uint, uint>& a) : QMap<uint, uint>(a) {}
1702+
1703+ inline HandleRolesMap& operator=(const QMap<uint, uint>& a)
1704+ {
1705+ *(static_cast<QMap<uint, uint>*>(this)) = a;
1706+ return *this;
1707+ }
1708+};
1709+
1710+Q_DECLARE_METATYPE(HandleRolesMap)
1711+
1712+class ChannelInterfaceRolesInterface : public Tp::AbstractInterface
1713+{
1714+ Q_OBJECT
1715+public:
1716+
1717+ /**
1718+ * Returns the name of the interface "org.freedesktop.Telepathy.Channel.Interface.Roles", which this class
1719+ * represents.
1720+ *
1721+ * \return The D-Bus interface name.
1722+ */
1723+ static inline QLatin1String staticInterfaceName()
1724+ {
1725+ return QLatin1String("org.freedesktop.Telepathy.Channel.Interface.Roles");
1726+ }
1727+
1728+ /**
1729+ * Creates a ChannelInterfaceRolesInterface associated with the given object on the session bus.
1730+ *
1731+ * \param busName Name of the service the object is on.
1732+ * \param objectPath Path to the object on the service.
1733+ * \param parent Passed to the parent class constructor.
1734+ */
1735+ ChannelInterfaceRolesInterface(
1736+ const QString& busName,
1737+ const QString& objectPath,
1738+ QObject* parent = 0
1739+ );
1740+
1741+ /**
1742+ * Creates a ChannelInterfaceRolesInterface associated with the given object on the given bus.
1743+ *
1744+ * \param connection The bus via which the object can be reached.
1745+ * \param busName Name of the service the object is on.
1746+ * \param objectPath Path to the object on the service.
1747+ * \param parent Passed to the parent class constructor.
1748+ */
1749+ ChannelInterfaceRolesInterface(
1750+ const QDBusConnection& connection,
1751+ const QString& busName,
1752+ const QString& objectPath,
1753+ QObject* parent = 0
1754+ );
1755+
1756+ /**
1757+ * Creates a ChannelInterfaceRolesInterface associated with the same object as the given proxy.
1758+ *
1759+ * \param proxy The proxy to use. It will also be the QObject::parent()
1760+ * for this object.
1761+ */
1762+ ChannelInterfaceRolesInterface(Tp::DBusProxy *proxy);
1763+
1764+ /**
1765+ * Creates a ChannelInterfaceRolesInterface associated with the same object as the given proxy.
1766+ * Additionally, the created proxy will have the same parent as the given
1767+ * proxy.
1768+ *
1769+ * \param mainInterface The proxy to use.
1770+ */
1771+ explicit ChannelInterfaceRolesInterface(const Tp::Client::ChannelInterface& mainInterface);
1772+
1773+ /**
1774+ * Creates a ChannelInterfaceRolesInterface associated with the same object as the given proxy.
1775+ * However, a different parent object can be specified.
1776+ *
1777+ * \param mainInterface The proxy to use.
1778+ * \param parent Passed to the parent class constructor.
1779+ */
1780+ ChannelInterfaceRolesInterface(const Tp::Client::ChannelInterface& mainInterface, QObject* parent);
1781+
1782+ /**
1783+ * Asynchronous getter for the remote object property \c Roles of type \c HandleRolesMap.
1784+ *
1785+ * \return A pending variant which will emit finished when the property has been
1786+ * retrieved.
1787+ */
1788+ inline Tp::PendingVariant *requestPropertyRoles() const
1789+ {
1790+ return internalRequestProperty(QLatin1String("Roles"));
1791+ }
1792+
1793+ /**
1794+ * Asynchronous getter for the remote object property \c CanUpdateRoles of type \c bool.
1795+ *
1796+ * \return A pending variant which will emit finished when the property has been
1797+ * retrieved.
1798+ */
1799+ inline Tp::PendingVariant *requestPropertyCanUpdateRoles() const
1800+ {
1801+ return internalRequestProperty(QLatin1String("CanUpdateRoles"));
1802+ }
1803+
1804+ /**
1805+ * Request all of the DBus properties on the interface.
1806+ *
1807+ * \return A pending variant map which will emit finished when the properties have
1808+ * been retrieved.
1809+ */
1810+ Tp::PendingVariantMap *requestAllProperties() const
1811+ {
1812+ return internalRequestAllProperties();
1813+ }
1814+
1815+ /**
1816+ * Synchronous version to get Roles property
1817+ */
1818+ HandleRolesMap getRoles() const;
1819+
1820+ /**
1821+ * Synchronous version to get CanUpdateRoles property
1822+ */
1823+ bool getCanUpdateRoles() const;
1824+
1825+public Q_SLOTS:
1826+ /**
1827+ * Begins a call to the D-Bus method \c UpdateRoles on the remote object.
1828+ *
1829+ * Update the roles in the server
1830+ *
1831+ */
1832+ inline QDBusPendingReply<> UpdateRoles(const HandleRolesMap &contactRoles, int timeout = -1)
1833+ {
1834+ if (!invalidationReason().isEmpty()) {
1835+ return QDBusPendingReply<>(QDBusMessage::createError(
1836+ invalidationReason(),
1837+ invalidationMessage()
1838+ ));
1839+ }
1840+
1841+ QDBusMessage callMessage = QDBusMessage::createMethodCall(this->service(), this->path(),
1842+ this->staticInterfaceName(), QLatin1String("UpdateRoles"));
1843+ callMessage << QVariant::fromValue(contactRoles);
1844+ return this->connection().asyncCall(callMessage, timeout);
1845+ }
1846+
1847+Q_SIGNALS:
1848+ /**
1849+ * Represents the signal \c RolesChanged on the remote object.
1850+ *
1851+ * Emitted when the state the roles of the channel has changed.
1852+ *
1853+ * \param added
1854+ *
1855+ * map of handles and related roles added
1856+ *
1857+ * \param removed
1858+ *
1859+ * map of handles and related roles removed
1860+ */
1861+ void RolesChanged(const HandleRolesMap &added, const HandleRolesMap& removed);
1862+
1863+protected:
1864+ virtual void invalidate(Tp::DBusProxy *, const QString &, const QString &);
1865+
1866+};
1867+
1868+#endif // CHANNELINTERFACEROLESINTERFACE_H
1869
1870=== modified file 'daemon/textchannelobserver.cpp'
1871--- daemon/textchannelobserver.cpp 2014-09-10 04:28:32 +0000
1872+++ daemon/textchannelobserver.cpp 2016-11-24 12:52:24 +0000
1873@@ -1,5 +1,5 @@
1874 /*
1875- * Copyright (C) 2012-2013 Canonical, Ltd.
1876+ * Copyright (C) 2012-2016 Canonical, Ltd.
1877 *
1878 * Authors:
1879 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1880@@ -43,6 +43,8 @@
1881 SIGNAL(pendingMessageRemoved(const Tp::ReceivedMessage&)),
1882 SLOT(onPendingMessageRemoved(const Tp::ReceivedMessage&)));
1883
1884+ Q_EMIT channelAvailable(textChannel);
1885+
1886 // process the messages that are already pending in the channel
1887 Q_FOREACH(const Tp::ReceivedMessage &message, textChannel->messageQueue()) {
1888 Q_EMIT messageReceived(textChannel, message);
1889
1890=== modified file 'daemon/textchannelobserver.h'
1891--- daemon/textchannelobserver.h 2013-07-12 14:30:18 +0000
1892+++ daemon/textchannelobserver.h 2016-11-24 12:52:24 +0000
1893@@ -1,5 +1,5 @@
1894 /*
1895- * Copyright (C) 2012-2013 Canonical, Ltd.
1896+ * Copyright (C) 2012-2016 Canonical, Ltd.
1897 *
1898 * Authors:
1899 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1900@@ -36,6 +36,7 @@
1901 void onTextChannelAvailable(Tp::TextChannelPtr textChannel);
1902
1903 Q_SIGNALS:
1904+ void channelAvailable(const Tp::TextChannelPtr textChannel);
1905 void messageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1906 void messageRead(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message);
1907 void messageSent(const Tp::TextChannelPtr textChannel, const Tp::Message &message, const QString &messageToken);
1908
1909=== added file 'plugins/sqlite/schema/v13.sql'
1910--- plugins/sqlite/schema/v13.sql 1970-01-01 00:00:00 +0000
1911+++ plugins/sqlite/schema/v13.sql 2016-11-24 12:52:24 +0000
1912@@ -0,0 +1,44 @@
1913+ALTER TABLE threads ADD COLUMN chatType tinyint;
1914+ALTER TABLE thread_participants ADD COLUMN alias varchar(255);
1915+ALTER TABLE thread_participants ADD COLUMN state tinyint;
1916+CREATE TABLE chat_room_info (
1917+ accountId varchar(255),
1918+ type tinyint,
1919+ threadId varchar(255),
1920+ roomName varchar(255),
1921+ server varchar(255),
1922+ creator varchar(255),
1923+ creationTimestamp datetime,
1924+ anonymous boolean,
1925+ inviteOnly boolean,
1926+ participantLimit integer,
1927+ moderated boolean,
1928+ title varchar(1024),
1929+ description varchar(1024),
1930+ persistent boolean,
1931+ private boolean,
1932+ passwordProtected boolean,
1933+ password varchar(512),
1934+ passwordHint varchar(512),
1935+ canUpdateConfiguration boolean,
1936+ subject varchar(1024),
1937+ actor varchar(512),
1938+ timestamp datetime
1939+);
1940+UPDATE threads SET chatType = 0;
1941+UPDATE threads SET chatType=1 WHERE (SELECT COUNT(participantId) from thread_participants WHERE thread_participants.threadId=threads.threadId and thread_participants.accountId=threads.accountId AND thread_participants.type=threads.type)=1;
1942+UPDATE thread_participants SET state = 0;
1943+
1944+DROP TRIGGER threads_delete_trigger;
1945+CREATE TRIGGER threads_delete_trigger AFTER DELETE ON threads
1946+FOR EACH ROW
1947+BEGIN
1948+ DELETE FROM thread_participants WHERE
1949+ accountId=old.accountId AND
1950+ threadId=old.threadId AND
1951+ type=old.type;
1952+ DELETE FROM chat_room_info WHERE
1953+ accountId=old.accountId AND
1954+ threadId=old.threadId AND
1955+ type=old.type;
1956+END;
1957
1958=== added file 'plugins/sqlite/schema/v14.sql'
1959--- plugins/sqlite/schema/v14.sql 1970-01-01 00:00:00 +0000
1960+++ plugins/sqlite/schema/v14.sql 2016-11-24 12:52:24 +0000
1961@@ -0,0 +1,82 @@
1962+DROP TRIGGER text_events_insert_trigger;
1963+CREATE TRIGGER text_events_insert_trigger AFTER INSERT ON text_events
1964+FOR EACH ROW WHEN new.messageType!=2
1965+BEGIN
1966+ UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE
1967+ accountId=new.accountId AND
1968+ threadId=new.threadId AND
1969+ messageType!=2)
1970+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
1971+ UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE
1972+ accountId=new.accountId AND threadId=new.threadId AND newEvent='1' AND messageType!=2)
1973+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
1974+ UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE
1975+ accountId=new.accountId AND
1976+ threadId=new.threadId AND
1977+ messageType!=2
1978+ ORDER BY timestamp DESC LIMIT 1)
1979+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
1980+ UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE
1981+ accountId=new.accountId AND
1982+ threadId=new.threadId AND
1983+ messageType!=2
1984+ ORDER BY timestamp DESC LIMIT 1)
1985+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
1986+END;
1987+
1988+DROP TRIGGER text_events_update_trigger;
1989+CREATE TRIGGER text_events_update_trigger AFTER UPDATE ON text_events
1990+FOR EACH ROW WHEN new.messageType!=2
1991+BEGIN
1992+ UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE
1993+ accountId=new.accountId AND
1994+ threadId=new.threadId AND
1995+ messageType!=2)
1996+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
1997+ UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE
1998+ accountId=new.accountId AND threadId=new.threadId AND newEvent='1' AND messageType!=2)
1999+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
2000+ UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE
2001+ accountId=new.accountId AND
2002+ threadId=new.threadId AND
2003+ messageType!=2
2004+ ORDER BY timestamp DESC LIMIT 1)
2005+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
2006+ UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE
2007+ accountId=new.accountId AND
2008+ threadId=new.threadId AND
2009+ messageType!=2
2010+ ORDER BY timestamp DESC LIMIT 1)
2011+ WHERE accountId=new.accountId AND threadId=new.threadId AND type=0;
2012+END;
2013+
2014+DROP TRIGGER text_events_delete_trigger;
2015+CREATE TRIGGER text_events_delete_trigger AFTER DELETE ON text_events
2016+FOR EACH ROW WHEN old.messageType!=2
2017+BEGIN
2018+ UPDATE threads SET count=(SELECT count(eventId) FROM text_events WHERE
2019+ accountId=old.accountId AND
2020+ threadId=old.threadId AND
2021+ messageType!=2)
2022+ WHERE accountId=old.accountId AND threadId=old.threadId AND type=0;
2023+ UPDATE threads SET unreadCount=(SELECT count(eventId) FROM text_events WHERE
2024+ accountId=old.accountId AND threadId=old.threadId AND newEvent='1' AND messageType!=2)
2025+ WHERE accountId=old.accountId AND threadId=old.threadId AND type=0;
2026+ UPDATE threads SET lastEventId=(SELECT eventId FROM text_events WHERE
2027+ accountId=old.accountId AND
2028+ threadId=old.threadId AND
2029+ messageType!=2
2030+ ORDER BY timestamp DESC LIMIT 1)
2031+ WHERE accountId=old.accountId AND threadId=old.threadId AND type=0;
2032+ UPDATE threads SET lastEventTimestamp=(SELECT timestamp FROM text_events WHERE
2033+ accountId=old.accountId AND
2034+ threadId=old.threadId AND
2035+ messageType!=2
2036+ ORDER BY timestamp DESC LIMIT 1)
2037+ WHERE accountId=old.accountId AND threadId=old.threadId AND type=0;
2038+ DELETE from text_event_attachments WHERE
2039+ accountId=old.accountId AND
2040+ threadId=old.threadId AND
2041+ eventId=old.eventId;
2042+END;
2043+
2044
2045=== added file 'plugins/sqlite/schema/v15.sql'
2046--- plugins/sqlite/schema/v15.sql 1970-01-01 00:00:00 +0000
2047+++ plugins/sqlite/schema/v15.sql 2016-11-24 12:52:24 +0000
2048@@ -0,0 +1,1 @@
2049+ALTER TABLE thread_participants ADD COLUMN roles tinyint;
2050
2051=== added file 'plugins/sqlite/schema/v16.sql'
2052--- plugins/sqlite/schema/v16.sql 1970-01-01 00:00:00 +0000
2053+++ plugins/sqlite/schema/v16.sql 2016-11-24 12:52:24 +0000
2054@@ -0,0 +1,2 @@
2055+ALTER TABLE chat_room_info ADD COLUMN joined boolean;
2056+ALTER TABLE chat_room_info ADD COLUMN selfRoles integer;
2057
2058=== added file 'plugins/sqlite/schema/v17.sql'
2059--- plugins/sqlite/schema/v17.sql 1970-01-01 00:00:00 +0000
2060+++ plugins/sqlite/schema/v17.sql 2016-11-24 12:52:24 +0000
2061@@ -0,0 +1,2 @@
2062+ALTER TABLE text_events ADD COLUMN informationType integer;
2063+UPDATE text_events SET informationType = 1;
2064
2065=== modified file 'plugins/sqlite/sqlitedatabase.cpp'
2066--- plugins/sqlite/sqlitedatabase.cpp 2015-10-06 12:54:04 +0000
2067+++ plugins/sqlite/sqlitedatabase.cpp 2016-11-24 12:52:24 +0000
2068@@ -207,6 +207,7 @@
2069
2070 QStringList statements;
2071 int existingVersion = 0;
2072+ int upgradeToVersion = 0;
2073
2074 if (create) {
2075 statements = parseSchemaFile(":/database/schema/schema.sql");
2076@@ -219,7 +220,7 @@
2077 }
2078
2079 existingVersion = query.value(0).toInt();
2080- int upgradeToVersion = existingVersion + 1;
2081+ upgradeToVersion = existingVersion + 1;
2082 while (upgradeToVersion <= mSchemaVersion) {
2083 statements += parseSchemaFile(QString(":/database/schema/v%1.sql").arg(QString::number(upgradeToVersion)));
2084 ++upgradeToVersion;
2085@@ -256,6 +257,22 @@
2086 return false;
2087 }
2088 }
2089+ if (existingVersion < 13) {
2090+ // convert all ofono groups to Room type depending if the mms option is enabled
2091+ QVariant mmsGroupChatEnabled = History::Utils::getUserValue("com.ubuntu.touch.AccountsService.Phone", "MmsGroupChatEnabled");
2092+ // we must not fail if we cannot reach accounts service.
2093+ if (mmsGroupChatEnabled.isValid()) {
2094+ // if mms is disabled all chats will be turned into broadcast, otherwise
2095+ // we turn them into Room
2096+ if (mmsGroupChatEnabled.toBool()) {
2097+ if (!convertOfonoGroupChatToRoom()) {
2098+ qCritical() << "Failed to update existing group chats to Room type.";
2099+ rollbackTransaction();
2100+ return false;
2101+ }
2102+ }
2103+ }
2104+ }
2105 }
2106
2107 finishTransaction();
2108@@ -392,3 +409,40 @@
2109
2110 return true;
2111 }
2112+
2113+bool SQLiteDatabase::convertOfonoGroupChatToRoom()
2114+{
2115+ QSqlQuery query(database());
2116+ QString queryText = "UPDATE threads SET chatType=2 WHERE accountId LIKE 'ofono/ofono%' AND (SELECT COUNT(participantId) from thread_participants WHERE thread_participants.threadId=threads.threadId and thread_participants.accountId=threads.accountId AND thread_participants.type=threads.type) > 1";
2117+
2118+ query.prepare(queryText);
2119+ if (!query.exec()) {
2120+ qWarning() << "Failed to update group chats to Room 1:" << query.executedQuery() << query.lastError();
2121+ return false;
2122+ }
2123+ query.clear();
2124+
2125+ // now insert a row in chat_room_info for each room
2126+ if (!query.exec("SELECT accountId, threadId from threads WHERE accountId LIKE 'ofono/ofono%' AND chatType=2")) {
2127+ qWarning() << "Failed to update group chats to Room 2:" << query.executedQuery() << query.lastError();
2128+ return false;
2129+ }
2130+
2131+ while (query.next()) {
2132+ QSqlQuery queryInsertRoom(database());
2133+ QString accountId = query.value(0).toString();
2134+ QString threadId = query.value(1).toString();
2135+ queryInsertRoom.prepare("INSERT INTO chat_room_info (accountId, threadId, type, joined) VALUES (:accountId,:threadId,:type,:joined)");
2136+ queryInsertRoom.bindValue(":accountId", accountId);
2137+ queryInsertRoom.bindValue(":threadId", threadId);
2138+ queryInsertRoom.bindValue(":type", History::EventTypeText);
2139+ queryInsertRoom.bindValue(":joined", true);
2140+ if(!queryInsertRoom.exec()) {
2141+ qWarning() << "Failed to update group chats to Room 3:" << queryInsertRoom.executedQuery() << queryInsertRoom.lastError();
2142+ return false;
2143+ }
2144+ queryInsertRoom.clear();
2145+ }
2146+ query.clear();
2147+}
2148+
2149
2150=== modified file 'plugins/sqlite/sqlitedatabase.h'
2151--- plugins/sqlite/sqlitedatabase.h 2015-09-02 18:14:15 +0000
2152+++ plugins/sqlite/sqlitedatabase.h 2016-11-24 12:52:24 +0000
2153@@ -50,6 +50,7 @@
2154
2155 // data upgrade functions
2156 bool changeTimestampsToUtc();
2157+ bool convertOfonoGroupChatToRoom();
2158
2159 private:
2160 explicit SQLiteDatabase(QObject *parent = 0);
2161
2162=== modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
2163--- plugins/sqlite/sqlitehistoryplugin.cpp 2015-11-20 11:40:36 +0000
2164+++ plugins/sqlite/sqlitehistoryplugin.cpp 2016-11-24 12:52:24 +0000
2165@@ -1,5 +1,5 @@
2166 /*
2167- * Copyright (C) 2013-2015 Canonical, Ltd.
2168+ * Copyright (C) 2013-2016 Canonical, Ltd.
2169 *
2170 * Authors:
2171 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2172@@ -35,17 +35,20 @@
2173 #include <QStringList>
2174 #include <QSqlError>
2175 #include <QDBusMetaType>
2176+#include <QCryptographicHash>
2177
2178-QString generateThreadMapKey(const History::Thread &thread)
2179-{
2180- return thread.accountId() + thread.threadId();
2181-}
2182+static const QLatin1String timestampFormat("yyyy-MM-ddTHH:mm:ss.zzz");
2183
2184 QString generateThreadMapKey(const QString &accountId, const QString &threadId)
2185 {
2186 return accountId + threadId;
2187 }
2188
2189+QString generateThreadMapKey(const History::Thread &thread)
2190+{
2191+ return generateThreadMapKey(thread.accountId(), thread.threadId());
2192+}
2193+
2194 SQLiteHistoryPlugin::SQLiteHistoryPlugin(QObject *parent) :
2195 QObject(parent), mInitialised(false)
2196 {
2197@@ -81,18 +84,18 @@
2198 // so instead we just convert to UTC here on the cache and convert back to local time
2199 // when returning
2200 QDateTime timestamp = QDateTime::fromString(properties[History::FieldTimestamp].toString(), Qt::ISODate);
2201- properties[History::FieldTimestamp] = timestamp.toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz");
2202+ properties[History::FieldTimestamp] = timestamp.toUTC().toString(timestampFormat);
2203
2204 // the same for readTimestamp
2205 timestamp = QDateTime::fromString(properties[History::FieldReadTimestamp].toString(), Qt::ISODate);
2206- properties[History::FieldReadTimestamp] = timestamp.toUTC().toString("yyyy-MM-ddTHH:mm:ss.zzz");
2207+ properties[History::FieldReadTimestamp] = timestamp.toUTC().toString(timestampFormat);
2208
2209 History::Thread thread = History::Thread::fromProperties(properties);
2210 const QString &threadKey = generateThreadMapKey(thread);
2211
2212 if (thread.type() != History::EventTypeText) {
2213 continue;
2214- } else if (!History::Utils::shouldGroupAccount(thread.accountId())) {
2215+ } else if (!History::Utils::shouldGroupThread(thread)) {
2216 // never group non phone accounts
2217 mConversationsCache[threadKey] = History::Threads() << thread;
2218 mConversationsCacheKeys[threadKey] = threadKey;
2219@@ -119,6 +122,9 @@
2220 const QString &conversationKey = it.key();
2221 History::Threads groupedThreads = it.value();
2222 Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
2223+ if (!History::Utils::shouldGroupThread(groupedThread) || thread.chatType() != groupedThread.chatType()) {
2224+ continue;
2225+ }
2226 found = History::Utils::compareNormalizedParticipants(thread.participants().identifiers(), groupedThread.participants().identifiers(), History::MatchPhoneNumber);
2227 if (found) {
2228 Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
2229@@ -176,7 +182,7 @@
2230 History::Thread thread = History::Thread::fromProperties(properties);
2231 QString threadKey = generateThreadMapKey(thread);
2232
2233- if (thread.type() != History::EventTypeText || !History::Utils::shouldGroupAccount(thread.accountId())) {
2234+ if (thread.type() != History::EventTypeText || !History::Utils::shouldGroupThread(thread)) {
2235 mConversationsCache.remove(threadKey);
2236 mConversationsCacheKeys.remove(threadKey);
2237 return;
2238@@ -259,7 +265,7 @@
2239 time.start();
2240 qDebug() << "---- HistoryService: start generating cached content";
2241 QSqlQuery query(SQLiteDatabase::instance()->database());
2242- if (!query.exec("SELECT DISTINCT accountId, normalizedId FROM thread_participants")) {
2243+ if (!query.exec("SELECT DISTINCT accountId, normalizedId, alias, state FROM thread_participants")) {
2244 qWarning() << "Failed to generate contact cache:" << query.lastError().text();
2245 return;
2246 }
2247@@ -267,9 +273,14 @@
2248 while (query.next()) {
2249 QString accountId = query.value(0).toString();
2250 QString participantId = query.value(1).toString();
2251+ QString alias = query.value(2).toString();
2252+ QVariantMap properties;
2253+ if (!alias.isEmpty()) {
2254+ properties[History::FieldAlias] = alias;
2255+ }
2256 // we don't care about the results, as long as the contact data is present in the cache for
2257 // future usage.
2258- History::ContactMatcher::instance()->contactInfo(accountId, participantId, true);
2259+ History::ContactMatcher::instance()->contactInfo(accountId, participantId, true, properties);
2260 }
2261
2262 updateGroupedThreadsCache();
2263@@ -295,6 +306,32 @@
2264 return new SQLiteHistoryEventView(this, type, sort, filter);
2265 }
2266
2267+QVariantMap SQLiteHistoryPlugin::threadForProperties(const QString &accountId,
2268+ History::EventType type,
2269+ const QVariantMap &properties,
2270+ History::MatchFlags matchFlags)
2271+{
2272+ if (properties.isEmpty()) {
2273+ return QVariantMap();
2274+ }
2275+
2276+ QSqlQuery query(SQLiteDatabase::instance()->database());
2277+
2278+ History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toUInt();
2279+
2280+ if (chatType == History::ChatTypeRoom) {
2281+ QString threadId = properties[History::FieldThreadId].toString();
2282+ if (threadId.isEmpty()) {
2283+ return QVariantMap();
2284+ }
2285+ return getSingleThread(type, accountId, threadId);
2286+ }
2287+
2288+ History::Participants participants = History::Participants::fromVariant(properties[History::FieldParticipantIds]);
2289+ // if chatType != Room, then we select the thread based on the participant list.
2290+ return threadForParticipants(accountId, type, participants.identifiers(), matchFlags);
2291+}
2292+
2293 QVariantMap SQLiteHistoryPlugin::threadForParticipants(const QString &accountId,
2294 History::EventType type,
2295 const QStringList &participants,
2296@@ -310,7 +347,9 @@
2297 // select all the threads the first participant is listed in, and from that list
2298 // check if any of the threads has all the other participants listed
2299 // FIXME: find a better way to do this
2300- QString queryString("SELECT threadId FROM thread_participants WHERE %1 AND type=:type AND accountId=:accountId");
2301+ QString queryString("SELECT threadId FROM thread_participants WHERE %1 AND type=:type AND accountId=:accountId "
2302+ "AND (SELECT chatType FROM threads WHERE threads.threadId=thread_participants.threadId AND "
2303+ " threads.type=thread_participants.type)!=:chatType");
2304
2305 // FIXME: for now we just compare differently when using MatchPhoneNumber
2306 QString firstParticipant = participants.first();
2307@@ -324,6 +363,9 @@
2308 query.bindValue(":participantId", firstParticipant);
2309 query.bindValue(":type", type);
2310 query.bindValue(":accountId", accountId);
2311+ // we don't want to accidentally return a chat room for a multi-recipient conversation
2312+ query.bindValue(":chatType", (int)History::ChatTypeRoom);
2313+
2314 if (!query.exec()) {
2315 qCritical() << "Error:" << query.lastError() << query.lastQuery();
2316 return QVariantMap();
2317@@ -469,58 +511,345 @@
2318 return result;
2319 }
2320
2321-// Writer
2322-QVariantMap SQLiteHistoryPlugin::createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants)
2323+bool SQLiteHistoryPlugin::updateRoomParticipants(const QString &accountId, const QString &threadId, History::EventType type, const QVariantList &participants)
2324+{
2325+ QSqlQuery query(SQLiteDatabase::instance()->database());
2326+ if (accountId.isEmpty() || threadId.isEmpty()) {
2327+ return false;
2328+ }
2329+
2330+ SQLiteDatabase::instance()->beginTransation();
2331+ QString deleteString("DELETE FROM thread_participants WHERE threadId=:threadId AND type=:type AND accountId=:accountId");
2332+ query.prepare(deleteString);
2333+ query.bindValue(":accountId", accountId);
2334+ query.bindValue(":threadId", threadId);
2335+ query.bindValue(":type", type);
2336+ if (!query.exec()) {
2337+ qCritical() << "Error removing old participants:" << query.lastError() << query.lastQuery();
2338+ SQLiteDatabase::instance()->rollbackTransaction();
2339+ return false;
2340+ }
2341+
2342+ // and insert the participants
2343+ Q_FOREACH(const QVariant &participantVariant, participants) {
2344+ QVariantMap participant = participantVariant.toMap();
2345+ query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId, alias, state, roles)"
2346+ "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId, :alias, :state, :roles)");
2347+ query.bindValue(":accountId", accountId);
2348+ query.bindValue(":threadId", threadId);
2349+ query.bindValue(":type", type);
2350+ query.bindValue(":participantId", participant["identifier"].toString());
2351+ query.bindValue(":normalizedId", participant["identifier"].toString());
2352+ query.bindValue(":alias", participant["alias"].toString());
2353+ query.bindValue(":state", participant["state"].toUInt());
2354+ query.bindValue(":roles", participant["roles"].toUInt());
2355+ if (!query.exec()) {
2356+ qCritical() << "Error:" << query.lastError() << query.lastQuery();
2357+ SQLiteDatabase::instance()->rollbackTransaction();
2358+ return false;
2359+ }
2360+ }
2361+
2362+ if (!SQLiteDatabase::instance()->finishTransaction()) {
2363+ qCritical() << "Failed to commit the transaction.";
2364+ return false;
2365+ }
2366+
2367+ QVariantMap existingThread = getSingleThread(type,
2368+ accountId,
2369+ threadId,
2370+ QVariantMap());
2371+
2372+ if (!existingThread.isEmpty()) {
2373+ addThreadsToCache(QList<QVariantMap>() << existingThread);
2374+ }
2375+
2376+ return true;
2377+}
2378+
2379+bool SQLiteHistoryPlugin::updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles)
2380+{
2381+ QSqlQuery query(SQLiteDatabase::instance()->database());
2382+ if (accountId.isEmpty() || threadId.isEmpty()) {
2383+ return false;
2384+ }
2385+
2386+ SQLiteDatabase::instance()->beginTransation();
2387+ Q_FOREACH(const QString &participantId, participantsRoles.keys()) {
2388+ query.prepare("UPDATE thread_participants SET roles=:roles WHERE accountId=:accountId AND threadId=:threadId AND type=:type AND participantId=:participantId");
2389+ query.bindValue(":roles", participantsRoles.value(participantId).toUInt());
2390+ query.bindValue(":accountId", accountId);
2391+ query.bindValue(":threadId", threadId);
2392+ query.bindValue(":type", type);
2393+ query.bindValue(":participantId", participantId);
2394+ if (!query.exec()) {
2395+ qCritical() << "Error:" << query.lastError() << query.lastQuery();
2396+ SQLiteDatabase::instance()->rollbackTransaction();
2397+ return false;
2398+ }
2399+ }
2400+
2401+ if (!SQLiteDatabase::instance()->finishTransaction()) {
2402+ qCritical() << "Failed to commit the transaction.";
2403+ return false;
2404+ }
2405+
2406+ QVariantMap existingThread = getSingleThread(type,
2407+ accountId,
2408+ threadId,
2409+ QVariantMap());
2410+
2411+ if (!existingThread.isEmpty()) {
2412+ addThreadsToCache(QList<QVariantMap>() << existingThread);
2413+ }
2414+
2415+ return true;
2416+}
2417+
2418+bool SQLiteHistoryPlugin::updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
2419+{
2420+ QSqlQuery query(SQLiteDatabase::instance()->database());
2421+
2422+ if (threadId.isEmpty() || accountId.isEmpty()) {
2423+ return false;
2424+ }
2425+
2426+ SQLiteDatabase::instance()->beginTransation();
2427+
2428+ QDateTime creationTimestamp = QDateTime::fromTime_t(properties["CreationTimestamp"].toUInt());
2429+ QDateTime timestamp = QDateTime::fromTime_t(properties["Timestamp"].toUInt());
2430+
2431+ QVariantMap propertyMapping;
2432+ propertyMapping["RoomName"] = "roomName";
2433+ propertyMapping["Server"] = "server";
2434+ propertyMapping["Creator"] = "creator";
2435+ propertyMapping["CreationTimestamp"] = "creationTimestamp";
2436+ propertyMapping["Anonymous"] = "anonymous";
2437+ propertyMapping["InviteOnly"] = "inviteOnly";
2438+ propertyMapping["Limit"] = "participantLimit";
2439+ propertyMapping["Moderated"] = "moderated";
2440+ propertyMapping["Title"] = "title";
2441+ propertyMapping["Description"] = "description";
2442+ propertyMapping["Persistent"] = "persistent";
2443+ propertyMapping["Private"] = "private";
2444+ propertyMapping["PasswordProtected"] = "passwordProtected";
2445+ propertyMapping["Password"] = "password";
2446+ propertyMapping["PasswordHint"] = "passwordHint";
2447+ propertyMapping["CanUpdateConfiguration"] = "canUpdateConfiguration";
2448+ propertyMapping["Subject"] = "subject";
2449+ propertyMapping["Actor"] = "actor";
2450+ propertyMapping["Timestamp"] = "timestamp";
2451+ propertyMapping["Joined"] = "joined";
2452+ propertyMapping["SelfRoles"] = "selfRoles";
2453+
2454+ QStringList changedPropListValues;
2455+ // populate sql query
2456+ Q_FOREACH (const QString &key, properties.keys()) {
2457+ if (propertyMapping.contains(key)) {
2458+ QString prop = propertyMapping[key].toString();
2459+ changedPropListValues << QString(prop+"=:"+ prop);
2460+ }
2461+ }
2462+ if (changedPropListValues.isEmpty()) {
2463+ return false;
2464+ }
2465+
2466+ query.prepare("UPDATE chat_room_info SET "+ changedPropListValues.join(", ")+" WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
2467+ query.bindValue(":accountId", accountId);
2468+ query.bindValue(":threadId", threadId);
2469+ query.bindValue(":type", (int) type);
2470+ query.bindValue(":roomName", properties["RoomName"].toString());
2471+ query.bindValue(":server", properties["Server"].toString());
2472+ query.bindValue(":creator", properties["Creator"].toString());
2473+ query.bindValue(":creationTimestamp", creationTimestamp.toUTC().toString(timestampFormat));
2474+ query.bindValue(":anonymous", properties["Anonymous"].toBool());
2475+ query.bindValue(":inviteOnly", properties["InviteOnly"].toBool());
2476+ query.bindValue(":participantLimit", properties["Limit"].toInt());
2477+ query.bindValue(":moderated", properties["Moderated"].toBool());
2478+ query.bindValue(":title", properties["Title"].toString());
2479+ query.bindValue(":description", properties["Description"].toString());
2480+ query.bindValue(":persistent", properties["Persistent"].toBool());
2481+ query.bindValue(":private", properties["Private"].toBool());
2482+ query.bindValue(":passwordProtected", properties["PasswordProtected"].toBool());
2483+ query.bindValue(":password", properties["Password"].toString());
2484+ query.bindValue(":passwordHint", properties["PasswordHint"].toString());
2485+ query.bindValue(":canUpdateConfiguration", properties["CanUpdateConfiguration"].toBool());
2486+ query.bindValue(":subject", properties["Subject"].toString());
2487+ query.bindValue(":actor", properties["Actor"].toString());
2488+ query.bindValue(":timestamp", timestamp.toUTC().toString(timestampFormat));
2489+ query.bindValue(":joined", properties["Joined"].toBool());
2490+ query.bindValue(":selfRoles", properties["SelfRoles"].toInt());
2491+
2492+ if (!query.exec()) {
2493+ qCritical() << "Error:" << query.lastError() << query.lastQuery();
2494+ SQLiteDatabase::instance()->rollbackTransaction();
2495+ return false;
2496+ }
2497+
2498+ if (!SQLiteDatabase::instance()->finishTransaction()) {
2499+ qCritical() << "Failed to commit the transaction.";
2500+ return false;
2501+ }
2502+
2503+ QVariantMap existingThread = getSingleThread(type,
2504+ accountId,
2505+ threadId,
2506+ QVariantMap());
2507+
2508+ if (!existingThread.isEmpty()) {
2509+ addThreadsToCache(QList<QVariantMap>() << existingThread);
2510+ }
2511+
2512+ return true;
2513+}
2514+
2515+QVariantMap SQLiteHistoryPlugin::createThreadForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties)
2516 {
2517 // WARNING: this function does NOT test to check if the thread is already created, you should check using HistoryReader::threadForParticipants()
2518
2519 QVariantMap thread;
2520+ History::Participants participants = History::Participants::fromVariant(properties[History::FieldParticipantIds]);
2521
2522 // Create a new thread
2523 // FIXME: define what the threadId will be
2524- QString threadId = participants.join("%");
2525+ QString threadId;
2526+ History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toInt();
2527+ QVariantMap chatRoomInfo;
2528+
2529+ SQLiteDatabase::instance()->beginTransation();
2530+
2531+ if (chatType == History::ChatTypeRoom) {
2532+ threadId = properties[History::FieldThreadId].toString();
2533+ // we cannot save chat room without threadId
2534+ if (accountId.isEmpty() || threadId.isEmpty()) {
2535+ SQLiteDatabase::instance()->rollbackTransaction();
2536+ return thread;
2537+ }
2538+ chatRoomInfo = properties[History::FieldChatRoomInfo].toMap();
2539+ QSqlQuery query(SQLiteDatabase::instance()->database());
2540+
2541+ QDateTime creationTimestamp = QDateTime::fromTime_t(chatRoomInfo["CreationTimestamp"].toUInt());
2542+ QDateTime timestamp = QDateTime::fromTime_t(chatRoomInfo["Timestamp"].toUInt());
2543+
2544+ query.prepare("INSERT INTO chat_room_info (accountId, threadId, type, roomName, server, creator, creationTimestamp, anonymous, inviteOnly, participantLimit, moderated, title, description, persistent, private, passwordProtected, password, passwordHint, canUpdateConfiguration, subject, actor, timestamp, joined, selfRoles) "
2545+ "VALUES (:accountId, :threadId, :type, :roomName, :server, :creator, :creationTimestamp, :anonymous, :inviteOnly, :participantLimit, :moderated, :title, :description, :persistent, :private, :passwordProtected, :password, :passwordHint, :canUpdateConfiguration, :subject, :actor, :timestamp, :joined, :selfRoles)");
2546+ query.bindValue(":accountId", accountId);
2547+ query.bindValue(":threadId", threadId);
2548+ query.bindValue(":type", (int) type);
2549+ query.bindValue(":roomName", chatRoomInfo["RoomName"].toString());
2550+ query.bindValue(":server", chatRoomInfo["Server"].toString());
2551+ query.bindValue(":creator", chatRoomInfo["Creator"].toString());
2552+ query.bindValue(":creationTimestamp", creationTimestamp.toUTC().toString(timestampFormat));
2553+ query.bindValue(":anonymous", chatRoomInfo["Anonymous"].toBool());
2554+ query.bindValue(":inviteOnly", chatRoomInfo["InviteOnly"].toBool());
2555+ query.bindValue(":participantLimit", chatRoomInfo["Limit"].toInt());
2556+ query.bindValue(":moderated", chatRoomInfo["Moderated"].toBool());
2557+ query.bindValue(":title", chatRoomInfo["Title"].toString());
2558+ query.bindValue(":description", chatRoomInfo["Description"].toString());
2559+ query.bindValue(":persistent", chatRoomInfo["Persistent"].toBool());
2560+ query.bindValue(":private", chatRoomInfo["Private"].toBool());
2561+ query.bindValue(":passwordProtected", chatRoomInfo["PasswordProtected"].toBool());
2562+ query.bindValue(":password", chatRoomInfo["Password"].toString());
2563+ query.bindValue(":passwordHint", chatRoomInfo["PasswordHint"].toString());
2564+ query.bindValue(":canUpdateConfiguration", chatRoomInfo["CanUpdateConfiguration"].toBool());
2565+ query.bindValue(":subject", chatRoomInfo["Subject"].toString());
2566+ query.bindValue(":actor", chatRoomInfo["Actor"].toString());
2567+ query.bindValue(":timestamp", timestamp.toUTC().toString(timestampFormat));
2568+ query.bindValue(":joined", chatRoomInfo["Joined"].toBool());
2569+ query.bindValue(":selfRoles", chatRoomInfo["SelfRoles"].toInt());
2570+
2571+ if (!query.exec()) {
2572+ qCritical() << "Error:" << query.lastError() << query.lastQuery();
2573+ SQLiteDatabase::instance()->rollbackTransaction();
2574+ return QVariantMap();
2575+ }
2576+ for (QVariantMap::iterator iter = chatRoomInfo.begin(); iter != chatRoomInfo.end();) {
2577+ if (!iter.value().isValid()) {
2578+ iter = chatRoomInfo.erase(iter);
2579+ } else {
2580+ iter++;
2581+ }
2582+ }
2583+ thread[History::FieldChatRoomInfo] = chatRoomInfo;
2584+ } else if (chatType == History::ChatTypeContact) {
2585+ threadId = participants.identifiers().join("%");
2586+ } else {
2587+ threadId = QString("broadcast:%1").arg(QString(QCryptographicHash::hash(participants.identifiers().join(";").toLocal8Bit(),QCryptographicHash::Md5).toHex()));;
2588+ }
2589
2590 QSqlQuery query(SQLiteDatabase::instance()->database());
2591- query.prepare("INSERT INTO threads (accountId, threadId, type, count, unreadCount)"
2592- "VALUES (:accountId, :threadId, :type, :count, :unreadCount)");
2593+ query.prepare("INSERT INTO threads (accountId, threadId, type, count, unreadCount, chatType, lastEventTimestamp)"
2594+ "VALUES (:accountId, :threadId, :type, :count, :unreadCount, :chatType, :lastEventTimestamp)");
2595 query.bindValue(":accountId", accountId);
2596 query.bindValue(":threadId", threadId);
2597 query.bindValue(":type", (int) type);
2598 query.bindValue(":count", 0);
2599 query.bindValue(":unreadCount", 0);
2600+ query.bindValue(":chatType", (int) chatType);
2601+ // make sure threads are created with an up-to-date timestamp
2602+ query.bindValue(":lastEventTimestamp", QDateTime::currentDateTimeUtc().toString(timestampFormat));
2603 if (!query.exec()) {
2604 qCritical() << "Error:" << query.lastError() << query.lastQuery();
2605+ SQLiteDatabase::instance()->rollbackTransaction();
2606 return QVariantMap();
2607 }
2608
2609 // and insert the participants
2610- Q_FOREACH(const QString &participant, participants) {
2611- query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId)"
2612- "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId)");
2613+ Q_FOREACH(const History::Participant &participant, participants) {
2614+ query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId, alias, state, roles)"
2615+ "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId, :alias, :state, :roles)");
2616 query.bindValue(":accountId", accountId);
2617 query.bindValue(":threadId", threadId);
2618 query.bindValue(":type", type);
2619- query.bindValue(":participantId", participant);
2620- query.bindValue(":normalizedId", History::Utils::normalizeId(accountId, participant));
2621+ query.bindValue(":participantId", participant.identifier());
2622+ query.bindValue(":normalizedId", History::Utils::normalizeId(accountId, participant.identifier()));
2623+ query.bindValue(":alias", participant.alias());
2624+ query.bindValue(":state", participant.state());
2625+ query.bindValue(":roles", participant.roles());
2626 if (!query.exec()) {
2627 qCritical() << "Error:" << query.lastError() << query.lastQuery();
2628+ SQLiteDatabase::instance()->rollbackTransaction();
2629 return QVariantMap();
2630 }
2631 }
2632
2633+ if (!SQLiteDatabase::instance()->finishTransaction()) {
2634+ qCritical() << "Failed to commit the transaction.";
2635+ return QVariantMap();
2636+ }
2637+
2638 // and finally create the thread
2639 thread[History::FieldAccountId] = accountId;
2640 thread[History::FieldThreadId] = threadId;
2641 thread[History::FieldType] = (int) type;
2642- thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2643+ QVariantList contactList;
2644+ QVariantList contactInfo = History::ContactMatcher::instance()->contactInfo(accountId, participants.identifiers(), true);
2645+ for (int i = 0; i < participants.count(); ++i) {
2646+ QVariantMap map = contactInfo[i].toMap();
2647+ History::Participant participant = participants[i];
2648+ map["state"] = participant.state();
2649+ map["roles"] = participant.roles();
2650+ contactList << map;
2651+ }
2652+ thread[History::FieldParticipants] = contactList;
2653 thread[History::FieldCount] = 0;
2654 thread[History::FieldUnreadCount] = 0;
2655+ thread[History::FieldChatType] = (int)chatType;
2656
2657 addThreadsToCache(QList<QVariantMap>() << thread);
2658
2659 return thread;
2660 }
2661
2662+// Writer
2663+QVariantMap SQLiteHistoryPlugin::createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants)
2664+{
2665+ QVariantMap properties;
2666+ properties[History::FieldParticipantIds] = participants;
2667+ properties[History::FieldChatType] = participants.size() != 1 ? History::ChatTypeNone : History::ChatTypeContact;
2668+ return createThreadForProperties(accountId, type, properties);
2669+}
2670+
2671 bool SQLiteHistoryPlugin::removeThread(const QVariantMap &thread)
2672 {
2673 QSqlQuery query(SQLiteDatabase::instance()->database());
2674@@ -550,16 +879,18 @@
2675 event[History::FieldThreadId].toString(),
2676 event[History::FieldEventId].toString());
2677
2678+ SQLiteDatabase::instance()->beginTransation();
2679+
2680 History::EventWriteResult result;
2681 if (existingEvent.isEmpty()) {
2682 // create new
2683- query.prepare("INSERT INTO text_events (accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject) "
2684- "VALUES (:accountId, :threadId, :eventId, :senderId, :timestamp, :newEvent, :message, :messageType, :messageStatus, :readTimestamp, :subject)");
2685+ query.prepare("INSERT INTO text_events (accountId, threadId, eventId, senderId, timestamp, newEvent, message, messageType, messageStatus, readTimestamp, subject, informationType)"
2686+ "VALUES (:accountId, :threadId, :eventId, :senderId, :timestamp, :newEvent, :message, :messageType, :messageStatus, :readTimestamp, :subject, :informationType)");
2687 result = History::EventWriteCreated;
2688 } else {
2689 // update existing event
2690- query.prepare("UPDATE text_events SET senderId=:senderId, timestamp=:timestamp, newEvent=:newEvent, message=:message, messageType=:messageType,"
2691- "messageStatus=:messageStatus, readTimestamp=:readTimestamp, subject=:subject WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId");
2692+ query.prepare("UPDATE text_events SET senderId=:senderId, timestamp=:timestamp, newEvent=:newEvent, message=:message, messageType=:messageType, informationType=:informationType, "
2693+ "messageStatus=:messageStatus, readTimestamp=:readTimestamp, subject=:subject, informationType=:informationType WHERE accountId=:accountId AND threadId=:threadId AND eventId=:eventId");
2694 result = History::EventWriteModified;
2695 }
2696
2697@@ -574,9 +905,11 @@
2698 query.bindValue(":messageStatus", event[History::FieldMessageStatus]);
2699 query.bindValue(":readTimestamp", event[History::FieldReadTimestamp].toDateTime().toUTC());
2700 query.bindValue(":subject", event[History::FieldSubject].toString());
2701+ query.bindValue(":informationType", event[History::FieldInformationType].toInt());
2702
2703 if (!query.exec()) {
2704 qCritical() << "Failed to save the text event: Error:" << query.lastError() << query.lastQuery();
2705+ SQLiteDatabase::instance()->rollbackTransaction();
2706 return History::EventWriteError;
2707 }
2708
2709@@ -592,6 +925,7 @@
2710 query.bindValue(":eventId", event[History::FieldEventId]);
2711 if (!query.exec()) {
2712 qCritical() << "Could not erase previous attachments. Error:" << query.lastError() << query.lastQuery();
2713+ SQLiteDatabase::instance()->rollbackTransaction();
2714 return History::EventWriteError;
2715 }
2716 }
2717@@ -608,11 +942,17 @@
2718 query.bindValue(":status", attachment[History::FieldStatus]);
2719 if (!query.exec()) {
2720 qCritical() << "Failed to save attachment to database" << query.lastError() << attachment;
2721+ SQLiteDatabase::instance()->rollbackTransaction();
2722 return History::EventWriteError;
2723 }
2724 }
2725 }
2726
2727+ if (!SQLiteDatabase::instance()->finishTransaction()) {
2728+ qCritical() << "Failed to commit transaction.";
2729+ return History::EventWriteError;
2730+ }
2731+
2732 if (result == History::EventWriteModified || result == History::EventWriteCreated) {
2733 QVariantMap existingThread = getSingleThread((History::EventType) event[History::FieldType].toInt(),
2734 event[History::FieldAccountId].toString(),
2735@@ -750,7 +1090,8 @@
2736 << "threads.threadId"
2737 << "threads.lastEventId"
2738 << "threads.count"
2739- << "threads.unreadCount";
2740+ << "threads.unreadCount"
2741+ << "threads.lastEventTimestamp";
2742
2743 // get the participants in the query already
2744 fields << "(SELECT group_concat(thread_participants.participantId, \"|,|\") "
2745@@ -758,13 +1099,23 @@
2746 "AND thread_participants.threadId=threads.threadId "
2747 "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as participants";
2748
2749+ fields << "(SELECT group_concat(thread_participants.state, \"|,|\") "
2750+ "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
2751+ "AND thread_participants.threadId=threads.threadId "
2752+ "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as state";
2753+
2754+ fields << "(SELECT group_concat(thread_participants.roles, \"|,|\") "
2755+ "FROM thread_participants WHERE thread_participants.accountId=threads.accountId "
2756+ "AND thread_participants.threadId=threads.threadId "
2757+ "AND thread_participants.type=threads.type GROUP BY accountId,threadId,type) as roles";
2758+
2759 QStringList extraFields;
2760 QString table;
2761
2762 switch (type) {
2763 case History::EventTypeText:
2764 table = "text_events";
2765- extraFields << "text_events.message" << "text_events.messageType" << "text_events.messageStatus" << "text_events.readTimestamp";
2766+ extraFields << "text_events.message" << "text_events.messageType" << "text_events.messageStatus" << "text_events.readTimestamp" << "chatType" << "text_events.subject" << "text_events.informationType";
2767 break;
2768 case History::EventTypeVoice:
2769 table = "voice_events";
2770@@ -773,7 +1124,6 @@
2771 }
2772
2773 fields << QString("%1.senderId").arg(table)
2774- << QString("%1.timestamp").arg(table)
2775 << QString("%1.newEvent").arg(table);
2776 fields << extraFields;
2777
2778@@ -820,13 +1170,31 @@
2779 thread[History::FieldEventId] = query.value(2);
2780 thread[History::FieldCount] = query.value(3);
2781 thread[History::FieldUnreadCount] = query.value(4);
2782- QStringList participants = query.value(5).toString().split("|,|");
2783- thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2784+ QStringList participants = query.value(6).toString().split("|,|", QString::SkipEmptyParts);
2785+ QList<int> participantStatus;
2786+ QStringList participantStatusString = query.value(7).toString().split("|,|", QString::SkipEmptyParts);
2787+ Q_FOREACH(const QString &statusString, participantStatusString) {
2788+ participantStatus << statusString.toUInt();
2789+ }
2790+ QStringList participantRolesString = query.value(8).toString().split("|,|", QString::SkipEmptyParts);
2791+ QList<int> participantRoles;
2792+ Q_FOREACH(const QString &rolesString, participantRolesString) {
2793+ participantRoles << rolesString.toUInt();
2794+ }
2795+ QVariantList contactList;
2796+ QVariantList contactInfo = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
2797+ for (int i = 0; i < contactInfo.count(); ++i) {
2798+ QVariantMap map = contactInfo[i].toMap();
2799+ map["state"] = participantStatus[i];
2800+ map["roles"] = participantRoles[i];
2801+ contactList << map;
2802+ }
2803+ thread[History::FieldParticipants] = contactList;
2804
2805 // the generic event fields
2806- thread[History::FieldSenderId] = query.value(6);
2807- thread[History::FieldTimestamp] = toLocalTimeString(query.value(7).toDateTime());
2808- thread[History::FieldNewEvent] = query.value(8).toBool();
2809+ thread[History::FieldSenderId] = query.value(9);
2810+ thread[History::FieldTimestamp] = toLocalTimeString(query.value(5).toDateTime());
2811+ thread[History::FieldNewEvent] = query.value(10).toBool();
2812
2813 // the next step is to get the last event
2814 switch (type) {
2815@@ -857,15 +1225,77 @@
2816 thread[History::FieldAttachments] = QVariant::fromValue(attachments);
2817 attachments.clear();
2818 }
2819- thread[History::FieldMessage] = query.value(9);
2820- thread[History::FieldMessageType] = query.value(10);
2821- thread[History::FieldMessageStatus] = query.value(11);
2822- thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(12).toDateTime());
2823+ thread[History::FieldMessage] = query.value(11);
2824+ thread[History::FieldMessageType] = query.value(12);
2825+ thread[History::FieldMessageStatus] = query.value(13);
2826+ thread[History::FieldReadTimestamp] = toLocalTimeString(query.value(14).toDateTime());
2827+ thread[History::FieldChatType] = query.value(15).toUInt();
2828+
2829+ if (thread[History::FieldChatType].toInt() == 2) {
2830+ QVariantMap chatRoomInfo;
2831+ QSqlQuery query1(SQLiteDatabase::instance()->database());
2832+
2833+ query1.prepare("SELECT roomName, server, creator, creationTimestamp, anonymous, inviteOnly, participantLimit, moderated, title, description, persistent, private, passwordProtected, password, passwordHint, canUpdateConfiguration, subject, actor, timestamp, joined, selfRoles FROM chat_room_info WHERE accountId=:accountId AND threadId=:threadId AND type=:type LIMIT 1");
2834+ query1.bindValue(":accountId", thread[History::FieldAccountId]);
2835+ query1.bindValue(":threadId", thread[History::FieldThreadId]);
2836+ query1.bindValue(":type", thread[History::FieldType].toInt());
2837+
2838+ if (!query1.exec()) {
2839+ qCritical() << "Failed to get chat room info for thread: Error:" << query1.lastError() << query1.lastQuery();
2840+ break;
2841+ }
2842+ query1.next();
2843+
2844+ if (query1.value(0).isValid())
2845+ chatRoomInfo["RoomName"] = query1.value(0);
2846+ if (query1.value(1).isValid())
2847+ chatRoomInfo["Server"] = query1.value(1);
2848+ if (query1.value(2).isValid())
2849+ chatRoomInfo["Creator"] = query1.value(2);
2850+ if (query1.value(3).isValid())
2851+ chatRoomInfo["CreationTimestamp"] = toLocalTimeString(query1.value(3).toDateTime());
2852+ if (query1.value(4).isValid())
2853+ chatRoomInfo["Anonymous"] = query1.value(4).toBool();
2854+ if (query1.value(5).isValid())
2855+ chatRoomInfo["InviteOnly"] = query1.value(5).toBool();
2856+ if (query1.value(6).isValid())
2857+ chatRoomInfo["Limit"] = query1.value(6).toInt();
2858+ if (query1.value(7).isValid())
2859+ chatRoomInfo["Moderated"] = query1.value(7).toBool();
2860+ if (query1.value(8).isValid())
2861+ chatRoomInfo["Title"] = query1.value(8);
2862+ if (query1.value(9).isValid())
2863+ chatRoomInfo["Description"] = query1.value(9);
2864+ if (query1.value(10).isValid())
2865+ chatRoomInfo["Persistent"] = query1.value(10).toBool();
2866+ if (query1.value(11).isValid())
2867+ chatRoomInfo["Private"] = query1.value(11).toBool();
2868+ if (query1.value(12).isValid())
2869+ chatRoomInfo["PasswordProtected"] = query1.value(12).toBool();
2870+ if (query1.value(13).isValid())
2871+ chatRoomInfo["Password"] = query1.value(13);
2872+ if (query1.value(14).isValid())
2873+ chatRoomInfo["PasswordHint"] = query1.value(14);
2874+ if (query1.value(15).isValid())
2875+ chatRoomInfo["CanUpdateConfiguration"] = query1.value(15).toBool();
2876+ if (query1.value(16).isValid())
2877+ chatRoomInfo["Subject"] = query1.value(16);
2878+ if (query1.value(17).isValid())
2879+ chatRoomInfo["Actor"] = query1.value(17);
2880+ if (query1.value(18).isValid())
2881+ chatRoomInfo["Timestamp"] = toLocalTimeString(query1.value(18).toDateTime());
2882+ if (query1.value(19).isValid())
2883+ chatRoomInfo["Joined"] = query1.value(19).toBool();
2884+ if (query1.value(20).isValid())
2885+ chatRoomInfo["SelfRoles"] = query1.value(20).toInt();
2886+
2887+ thread[History::FieldChatRoomInfo] = chatRoomInfo;
2888+ }
2889 break;
2890 case History::EventTypeVoice:
2891- thread[History::FieldMissed] = query.value(10);
2892- thread[History::FieldDuration] = query.value(9);
2893- thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(11).toString(), true);
2894+ thread[History::FieldMissed] = query.value(12);
2895+ thread[History::FieldDuration] = query.value(11);
2896+ thread[History::FieldRemoteParticipant] = History::ContactMatcher::instance()->contactInfo(accountId, query.value(13).toString(), true);
2897 break;
2898 }
2899 threads << thread;
2900@@ -889,7 +1319,7 @@
2901 case History::EventTypeText:
2902 participantsField = participantsField.arg("text_events", QString::number(type));
2903 queryText = QString("SELECT accountId, threadId, eventId, senderId, timestamp, newEvent, %1, "
2904- "message, messageType, messageStatus, readTimestamp, subject FROM text_events %2 %3").arg(participantsField, modifiedCondition, order);
2905+ "message, messageType, messageStatus, readTimestamp, subject, informationType FROM text_events %2 %3").arg(participantsField, modifiedCondition, order);
2906 break;
2907 case History::EventTypeVoice:
2908 participantsField = participantsField.arg("voice_events", QString::number(type));
2909@@ -960,6 +1390,10 @@
2910 event[History::FieldMessageType] = query.value(8);
2911 event[History::FieldMessageStatus] = query.value(9);
2912 event[History::FieldReadTimestamp] = toLocalTimeString(query.value(10).toDateTime());
2913+ if (!query.value(11).toString().isEmpty()) {
2914+ event[History::FieldSubject] = query.value(11).toString();
2915+ }
2916+ event[History::FieldInformationType] = query.value(12).toInt();
2917 break;
2918 case History::EventTypeVoice:
2919 event[History::FieldDuration] = query.value(7).toInt();
2920@@ -975,7 +1409,7 @@
2921
2922 QString SQLiteHistoryPlugin::toLocalTimeString(const QDateTime &timestamp)
2923 {
2924- return QDateTime(timestamp.date(), timestamp.time(), Qt::UTC).toLocalTime().toString("yyyy-MM-ddTHH:mm:ss.zzz");
2925+ return QDateTime(timestamp.date(), timestamp.time(), Qt::UTC).toLocalTime().toString(timestampFormat);
2926 }
2927
2928 QString SQLiteHistoryPlugin::filterToString(const History::Filter &filter, QVariantMap &bindValues, const QString &propertyPrefix) const
2929
2930=== modified file 'plugins/sqlite/sqlitehistoryplugin.h'
2931--- plugins/sqlite/sqlitehistoryplugin.h 2015-10-20 19:20:00 +0000
2932+++ plugins/sqlite/sqlitehistoryplugin.h 2016-11-24 12:52:24 +0000
2933@@ -1,5 +1,5 @@
2934 /*
2935- * Copyright (C) 2013-2015 Canonical, Ltd.
2936+ * Copyright (C) 2013-2016 Canonical, Ltd.
2937 *
2938 * Authors:
2939 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2940@@ -55,6 +55,10 @@
2941 History::EventType type,
2942 const QStringList &participants,
2943 History::MatchFlags matchFlags = History::MatchCaseSensitive);
2944+ QVariantMap threadForProperties(const QString &accountId,
2945+ History::EventType type,
2946+ const QVariantMap &properties,
2947+ History::MatchFlags matchFlags = History::MatchCaseSensitive);
2948
2949 QList<QVariantMap> eventsForThread(const QVariantMap &thread);
2950
2951@@ -62,7 +66,12 @@
2952 QVariantMap getSingleEvent(History::EventType type, const QString &accountId, const QString &threadId, const QString &eventId);
2953
2954 // Writer part of the plugin
2955+ QVariantMap createThreadForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties);
2956 QVariantMap createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants);
2957+
2958+ bool updateRoomParticipants(const QString &accountId, const QString &threadId, History::EventType type, const QVariantList &participants);
2959+ bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles);
2960+ bool updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList());
2961 bool removeThread(const QVariantMap &thread);
2962
2963 History::EventWriteResult writeTextEvent(const QVariantMap &event);
2964
2965=== modified file 'plugins/sqlite/sqlitehistorythreadview.cpp'
2966--- plugins/sqlite/sqlitehistorythreadview.cpp 2015-09-21 20:05:06 +0000
2967+++ plugins/sqlite/sqlitehistorythreadview.cpp 2016-11-24 12:52:24 +0000
2968@@ -35,7 +35,6 @@
2969 : History::PluginThreadView(), mPlugin(plugin), mType(type), mSort(sort),
2970 mFilter(filter), mPageSize(15), mQuery(SQLiteDatabase::instance()->database()), mOffset(0), mValid(true), mQueryProperties(properties)
2971 {
2972- qDebug() << __PRETTY_FUNCTION__;
2973 mTemporaryTable = QString("threadview%1%2").arg(QString::number((qulonglong)this), QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmsszzz"));
2974 mQuery.setForwardOnly(true);
2975
2976@@ -69,9 +68,6 @@
2977 Q_EMIT Invalidated();
2978 return;
2979 }
2980-
2981- mQuery.exec(QString("SELECT count(*) FROM %1").arg(mTemporaryTable));
2982- mQuery.next();
2983 }
2984
2985 SQLiteHistoryThreadView::~SQLiteHistoryThreadView()
2986@@ -84,7 +80,6 @@
2987
2988 QList<QVariantMap> SQLiteHistoryThreadView::NextPage()
2989 {
2990- qDebug() << __PRETTY_FUNCTION__;
2991 QList<QVariantMap> threads;
2992
2993 // now prepare for selecting from it
2994
2995=== modified file 'src/channelobserver.cpp'
2996--- src/channelobserver.cpp 2015-10-06 17:03:38 +0000
2997+++ src/channelobserver.cpp 2016-11-24 12:52:24 +0000
2998@@ -40,6 +40,8 @@
2999 Tp::ChannelClassSpecList specList;
3000 specList << Tp::ChannelClassSpec::audioCall();
3001 specList << Tp::ChannelClassSpec::textChat();
3002+ specList << Tp::ChannelClassSpec::textChatroom();
3003+ specList << Tp::ChannelClassSpec::unnamedTextChat();
3004
3005 return specList;
3006 }
3007@@ -58,7 +60,6 @@
3008 Q_UNUSED(requestsSatisfied)
3009 Q_UNUSED(observerInfo)
3010
3011- qDebug() << __PRETTY_FUNCTION__;
3012 Q_FOREACH (Tp::ChannelPtr channel, channels) {
3013 // tp-qt has not support for the SMS interface
3014 if (channel->immutableProperties().contains(TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash"))) {
3015
3016=== modified file 'src/contactmatcher.cpp'
3017--- src/contactmatcher.cpp 2015-10-07 20:21:54 +0000
3018+++ src/contactmatcher.cpp 2016-11-24 12:52:24 +0000
3019@@ -1,5 +1,5 @@
3020 /*
3021- * Copyright (C) 2014-2015 Canonical, Ltd.
3022+ * Copyright (C) 2014-2016 Canonical, Ltd.
3023 *
3024 * Authors:
3025 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3026@@ -95,7 +95,7 @@
3027 *
3028 * Note that synchronous requests should only be placed after \ref TelepathyHelper is ready.
3029 */
3030-QVariantMap ContactMatcher::contactInfo(const QString &accountId, const QString &identifier, bool synchronous)
3031+QVariantMap ContactMatcher::contactInfo(const QString &accountId, const QString &identifier, bool synchronous, const QVariantMap &properties)
3032 {
3033 InternalContactMap &internalMap = mContactMap[accountId];
3034
3035@@ -103,8 +103,9 @@
3036 if (internalMap.contains(identifier)) {
3037 return internalMap[identifier];
3038 }
3039-
3040+
3041 QVariantMap map;
3042+
3043 // and if there was no match, asynchronously request the info, and return an empty map for now
3044 if (History::TelepathyHelper::instance()->ready()) {
3045 map = requestContactInfo(accountId, identifier, synchronous);
3046@@ -114,6 +115,15 @@
3047 }
3048 map[History::FieldIdentifier] = identifier;
3049 map[History::FieldAccountId] = accountId;
3050+
3051+ QMapIterator<QString, QVariant> i(properties);
3052+ while (i.hasNext()) {
3053+ i.next();
3054+ if (!map.contains(i.key())) {
3055+ map[i.key()] = i.value();
3056+ }
3057+ }
3058+
3059 mContactMap[accountId][identifier] = map;
3060 return map;
3061 }
3062
3063=== modified file 'src/contactmatcher_p.h'
3064--- src/contactmatcher_p.h 2015-10-06 12:59:03 +0000
3065+++ src/contactmatcher_p.h 2016-11-24 12:52:24 +0000
3066@@ -1,5 +1,5 @@
3067 /*
3068- * Copyright (C) 2014-2015 Canonical, Ltd.
3069+ * Copyright (C) 2014-2016 Canonical, Ltd.
3070 *
3071 * Authors:
3072 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3073@@ -45,7 +45,7 @@
3074 Q_OBJECT
3075 public:
3076 static ContactMatcher *instance(QContactManager *manager = 0);
3077- QVariantMap contactInfo(const QString &accountId, const QString &identifier, bool synchronous = false);
3078+ QVariantMap contactInfo(const QString &accountId, const QString &identifier, bool synchronous = false, const QVariantMap &properties = QVariantMap());
3079 QVariantList contactInfo(const QString &accountId, const QStringList &identifiers, bool synchronous = false);
3080
3081 // this will only watch for contact changes affecting the identifier, but won't fetch contact info
3082
3083=== modified file 'src/manager.cpp'
3084--- src/manager.cpp 2015-10-01 19:44:45 +0000
3085+++ src/manager.cpp 2016-11-24 12:52:24 +0000
3086@@ -1,5 +1,5 @@
3087 /*
3088- * Copyright (C) 2013 Canonical, Ltd.
3089+ * Copyright (C) 2013-2016 Canonical, Ltd.
3090 *
3091 * Authors:
3092 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3093@@ -135,7 +135,23 @@
3094 {
3095 Q_D(Manager);
3096
3097- return d->dbus->threadForParticipants(accountId, type, participants, matchFlags, create);
3098+ QVariantMap properties;
3099+ properties[History::FieldParticipantIds] = participants;
3100+ if (participants.size() == 1) {
3101+ properties[History::FieldChatType] = History::ChatTypeContact;
3102+ }
3103+ return d->dbus->threadForProperties(accountId, type, properties, matchFlags, create);
3104+}
3105+
3106+Thread Manager::threadForProperties(const QString &accountId,
3107+ EventType type,
3108+ const QVariantMap &properties,
3109+ MatchFlags matchFlags,
3110+ bool create)
3111+{
3112+ Q_D(Manager);
3113+
3114+ return d->dbus->threadForProperties(accountId, type, properties, matchFlags, create);
3115 }
3116
3117 Thread Manager::getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
3118
3119=== modified file 'src/manager.h'
3120--- src/manager.h 2015-10-01 19:44:45 +0000
3121+++ src/manager.h 2016-11-24 12:52:24 +0000
3122@@ -1,5 +1,5 @@
3123 /*
3124- * Copyright (C) 2013 Canonical, Ltd.
3125+ * Copyright (C) 2013-2016 Canonical, Ltd.
3126 *
3127 * Authors:
3128 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3129@@ -61,6 +61,12 @@
3130 const QStringList &participants,
3131 History::MatchFlags matchFlags = History::MatchCaseSensitive,
3132 bool create = false);
3133+ Thread threadForProperties(const QString &accountId,
3134+ EventType type,
3135+ const QVariantMap &properties,
3136+ History::MatchFlags matchFlags = History::MatchCaseSensitive,
3137+ bool create = false);
3138+
3139 Thread getSingleThread(EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties = QVariantMap());
3140
3141 bool writeEvents(const History::Events &events);
3142
3143=== modified file 'src/managerdbus.cpp'
3144--- src/managerdbus.cpp 2015-09-23 15:08:07 +0000
3145+++ src/managerdbus.cpp 2016-11-24 12:52:24 +0000
3146@@ -1,5 +1,5 @@
3147 /*
3148- * Copyright (C) 2013 Canonical, Ltd.
3149+ * Copyright (C) 2013-2016 Canonical, Ltd.
3150 *
3151 * Authors:
3152 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3153@@ -64,9 +64,21 @@
3154 MatchFlags matchFlags,
3155 bool create)
3156 {
3157+ QVariantMap properties;
3158+ properties[History::FieldParticipantIds] = participants;
3159+
3160+ return threadForProperties(accountId, type, properties, matchFlags, create);
3161+}
3162+
3163+Thread ManagerDBus::threadForProperties(const QString &accountId,
3164+ EventType type,
3165+ const QVariantMap &properties,
3166+ MatchFlags matchFlags,
3167+ bool create)
3168+{
3169 Thread thread;
3170 // FIXME: move to async call if possible
3171- QDBusReply<QVariantMap> reply = mInterface.call("ThreadForParticipants", accountId, (int) type, participants, (int)matchFlags, create);
3172+ QDBusReply<QVariantMap> reply = mInterface.call("ThreadForProperties", accountId, (int) type, properties, (int)matchFlags, create);
3173 if (reply.isValid()) {
3174 QVariantMap properties = reply.value();
3175 thread = Thread::fromProperties(properties);
3176
3177=== modified file 'src/managerdbus_p.h'
3178--- src/managerdbus_p.h 2015-09-23 15:08:07 +0000
3179+++ src/managerdbus_p.h 2016-11-24 12:52:24 +0000
3180@@ -1,5 +1,5 @@
3181 /*
3182- * Copyright (C) 2013 Canonical, Ltd.
3183+ * Copyright (C) 2013-2016 Canonical, Ltd.
3184 *
3185 * Authors:
3186 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3187@@ -45,6 +45,12 @@
3188 History::MatchFlags matchFlags,
3189 bool create);
3190
3191+ Thread threadForProperties(const QString &accountId,
3192+ EventType type,
3193+ const QVariantMap &properties,
3194+ History::MatchFlags matchFlags,
3195+ bool create);
3196+
3197 bool writeEvents(const History::Events &events);
3198 bool removeThreads(const Threads &threads);
3199 bool removeEvents(const Events &events);
3200
3201=== modified file 'src/participant.cpp'
3202--- src/participant.cpp 2016-01-04 18:28:34 +0000
3203+++ src/participant.cpp 2016-11-24 12:52:24 +0000
3204@@ -1,5 +1,5 @@
3205 /*
3206- * Copyright (C) 2015 Canonical, Ltd.
3207+ * Copyright (C) 2015-2016 Canonical, Ltd.
3208 *
3209 * Authors:
3210 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3211@@ -36,9 +36,11 @@
3212 const QString &theContactId,
3213 const QString &theAlias,
3214 const QString &theAvatar,
3215+ const uint theState,
3216+ const uint theRoles,
3217 const QVariantMap &theDetailProperties) :
3218 accountId(theAccountId), identifier(theIdentifier), contactId(theContactId),
3219- alias(theAlias), avatar(theAvatar), detailProperties(theDetailProperties)
3220+ alias(theAlias), avatar(theAvatar), state(theState), roles(theRoles), detailProperties(theDetailProperties)
3221 {
3222 }
3223
3224@@ -51,8 +53,8 @@
3225 {
3226 }
3227
3228-Participant::Participant(const QString &accountId, const QString &identifier, const QString &contactId, const QString &alias, const QString &avatar, const QVariantMap &detailProperties)
3229- : d_ptr(new ParticipantPrivate(accountId, identifier, contactId, alias, avatar, detailProperties))
3230+Participant::Participant(const QString &accountId, const QString &identifier, const QString &contactId, const QString &alias, const QString &avatar, uint state, uint roles, const QVariantMap &detailProperties)
3231+ : d_ptr(new ParticipantPrivate(accountId, identifier, contactId, alias, avatar, state, roles, detailProperties))
3232 {
3233 }
3234
3235@@ -104,6 +106,18 @@
3236 return d->avatar;
3237 }
3238
3239+uint Participant::state() const
3240+{
3241+ Q_D(const Participant);
3242+ return d->state;
3243+}
3244+
3245+uint Participant::roles() const
3246+{
3247+ Q_D(const Participant);
3248+ return d->roles;
3249+}
3250+
3251 QVariantMap Participant::detailProperties() const
3252 {
3253 Q_D(const Participant);
3254@@ -140,6 +154,8 @@
3255 map[FieldContactId] = d->contactId;
3256 map[FieldAlias] = d->alias;
3257 map[FieldAvatar] = d->avatar;
3258+ map[FieldParticipantState] = d->state;
3259+ map[FieldParticipantRoles] = d->roles;
3260 map[FieldDetailProperties] = d->detailProperties;
3261
3262 return map;
3263@@ -157,6 +173,8 @@
3264 QString contactId = properties[FieldContactId].toString();
3265 QString alias = properties[FieldAlias].toString();
3266 QString avatar = properties[FieldAvatar].toString();
3267+ uint state = properties[FieldParticipantState].toUInt();
3268+ uint roles = properties[FieldParticipantRoles].toUInt();
3269 QVariantMap detailProperties;
3270 QVariant detailPropertiesVariant = properties[FieldDetailProperties];
3271 if (detailPropertiesVariant.canConvert<QVariantMap>()) {
3272@@ -169,7 +187,7 @@
3273 }
3274 }
3275
3276- return Participant(accountId, identifier, contactId, alias, avatar, detailProperties);
3277+ return Participant(accountId, identifier, contactId, alias, avatar, state, roles, detailProperties);
3278 }
3279
3280 QStringList Participants::identifiers() const
3281@@ -184,7 +202,9 @@
3282 Participants Participants::fromVariant(const QVariant &variant)
3283 {
3284 Participants participants;
3285- if (variant.canConvert<QVariantList>()) {
3286+ if (variant.type() == QVariant::StringList) {
3287+ participants = Participants::fromStringList(variant.toStringList());
3288+ } else if (variant.canConvert<QVariantList>()) {
3289 participants = Participants::fromVariantList(variant.toList());
3290 } else if (variant.canConvert<QDBusArgument>()) {
3291 QDBusArgument argument = variant.value<QDBusArgument>();
3292@@ -193,6 +213,17 @@
3293 return participants;
3294 }
3295
3296+Participants Participants::fromStringList(const QStringList &list)
3297+{
3298+ Participants participants;
3299+ Q_FOREACH(const QString& participantId, list) {
3300+ QVariantMap properties;
3301+ properties[FieldIdentifier] = participantId;
3302+ participants << Participant::fromProperties(properties);
3303+ }
3304+ return participants;
3305+}
3306+
3307 Participants Participants::fromVariantList(const QVariantList &list)
3308 {
3309 Participants participants;
3310@@ -211,6 +242,17 @@
3311 return list;
3312 }
3313
3314+Participants Participants::filterByState(uint state) const
3315+{
3316+ Participants filtered;
3317+ Q_FOREACH(const Participant &participant, *this) {
3318+ if (participant.state() == state) {
3319+ filtered << participant;
3320+ }
3321+ }
3322+ return filtered;
3323+}
3324+
3325 const QDBusArgument &operator>>(const QDBusArgument &argument, Participants &participants)
3326 {
3327 argument.beginArray();
3328
3329=== modified file 'src/participant.h'
3330--- src/participant.h 2015-09-28 23:13:21 +0000
3331+++ src/participant.h 2016-11-24 12:52:24 +0000
3332@@ -1,5 +1,5 @@
3333 /*
3334- * Copyright (C) 2015 Canonical, Ltd.
3335+ * Copyright (C) 2015-2016 Canonical, Ltd.
3336 *
3337 * Authors:
3338 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3339@@ -44,6 +44,8 @@
3340 const QString &contactId = QString::null,
3341 const QString &alias = QString::null,
3342 const QString &avatar = QString::null,
3343+ uint state = 0,
3344+ uint roles = 0,
3345 const QVariantMap &detailProperties = QVariantMap());
3346 Participant(const Participant &other);
3347 Participant& operator=(const Participant &other);
3348@@ -54,6 +56,8 @@
3349 QString contactId() const;
3350 QString alias() const;
3351 QString avatar() const;
3352+ uint state() const;
3353+ uint roles() const;
3354 QVariantMap detailProperties() const;
3355
3356 bool isNull() const;
3357@@ -75,8 +79,9 @@
3358 QStringList identifiers() const;
3359 static Participants fromVariant(const QVariant &variant);
3360 static Participants fromVariantList(const QVariantList &list);
3361+ static Participants fromStringList(const QStringList &list);
3362 QVariantList toVariantList() const;
3363-
3364+ History::Participants filterByState(uint state) const;
3365 };
3366
3367 const QDBusArgument &operator>>(const QDBusArgument &argument, Participants &participants);
3368
3369=== modified file 'src/participant_p.h'
3370--- src/participant_p.h 2015-09-28 18:53:49 +0000
3371+++ src/participant_p.h 2016-11-24 12:52:24 +0000
3372@@ -39,6 +39,8 @@
3373 const QString &theContactId = QString::null,
3374 const QString &theAlias = QString::null,
3375 const QString &theAvatar = QString::null,
3376+ uint theState = 0,
3377+ uint theRoles = 0,
3378 const QVariantMap &theDetailProperties = QVariantMap());
3379 virtual ~ParticipantPrivate();
3380
3381@@ -47,6 +49,8 @@
3382 QString contactId;
3383 QString alias;
3384 QString avatar;
3385+ uint state;
3386+ uint roles;
3387 QVariantMap detailProperties;
3388 };
3389
3390
3391=== modified file 'src/plugin.h'
3392--- src/plugin.h 2015-09-28 22:23:09 +0000
3393+++ src/plugin.h 2016-11-24 12:52:24 +0000
3394@@ -1,5 +1,5 @@
3395 /*
3396- * Copyright (C) 2013 Canonical, Ltd.
3397+ * Copyright (C) 2013-2016 Canonical, Ltd.
3398 *
3399 * Authors:
3400 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3401@@ -61,11 +61,19 @@
3402 EventType type,
3403 const QStringList &participants,
3404 History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
3405+ virtual QVariantMap threadForProperties(const QString &accountId,
3406+ EventType type,
3407+ const QVariantMap &properties,
3408+ History::MatchFlags matchFlags = History::MatchCaseSensitive) = 0;
3409
3410 virtual QList<QVariantMap> eventsForThread(const QVariantMap &thread) = 0;
3411
3412 // Writer part of the plugin
3413 virtual QVariantMap createThreadForParticipants(const QString &accountId, EventType type, const QStringList &participants) { return QVariantMap(); }
3414+ virtual QVariantMap createThreadForProperties(const QString &accountId, EventType type, const QVariantMap &properties) { return QVariantMap(); }
3415+ virtual bool updateRoomParticipants(const QString &accountId, const QString &threadId, History::EventType type, const QVariantList &participants) { return false; };
3416+ virtual bool updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles) { return false; };
3417+ virtual bool updateRoomInfo(const QString &accountId, const QString &threadId, EventType type, const QVariantMap &properties, const QStringList &invalidated = QStringList()) { return false; };
3418 virtual bool removeThread(const QVariantMap &thread) { return false; }
3419
3420 virtual EventWriteResult writeTextEvent(const QVariantMap &event) { return EventWriteError; }
3421
3422=== modified file 'src/plugineventview.cpp'
3423--- src/plugineventview.cpp 2013-11-19 17:52:53 +0000
3424+++ src/plugineventview.cpp 2016-11-24 12:52:24 +0000
3425@@ -56,7 +56,6 @@
3426
3427 void PluginEventView::Destroy()
3428 {
3429- qDebug() << __PRETTY_FUNCTION__;
3430 Q_D(PluginEventView);
3431 deleteLater();
3432 }
3433
3434=== modified file 'src/pluginthreadview.cpp'
3435--- src/pluginthreadview.cpp 2013-11-19 17:52:53 +0000
3436+++ src/pluginthreadview.cpp 2016-11-24 12:52:24 +0000
3437@@ -56,7 +56,6 @@
3438
3439 void PluginThreadView::Destroy()
3440 {
3441- qDebug() << __PRETTY_FUNCTION__;
3442 Q_D(PluginThreadView);
3443 deleteLater();
3444 }
3445
3446=== modified file 'src/textevent.cpp'
3447--- src/textevent.cpp 2015-09-28 23:13:21 +0000
3448+++ src/textevent.cpp 2016-11-24 12:52:24 +0000
3449@@ -47,10 +47,11 @@
3450 MessageStatus theMessageStatus,
3451 const QDateTime &theReadTimestamp,
3452 const QString &theSubject,
3453+ InformationType theInformationType,
3454 const TextEventAttachments &theAttachments, const Participants &theParticipants) :
3455 EventPrivate(theAccountId, theThreadId, theEventId, theSender, theTimestamp, theNewEvent, theParticipants),
3456 message(theMessage), messageType(theMessageType), messageStatus(theMessageStatus),
3457- readTimestamp(theReadTimestamp), subject(theSubject), attachments(theAttachments)
3458+ readTimestamp(theReadTimestamp), subject(theSubject), informationType(theInformationType), attachments(theAttachments)
3459 {
3460 }
3461
3462@@ -72,6 +73,7 @@
3463 map[FieldMessageStatus] = (int)messageStatus;
3464 map[FieldReadTimestamp] = readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz");
3465 map[FieldSubject] = subject;
3466+ map[FieldInformationType] = informationType;
3467
3468 QList<QVariantMap> attachmentsMap;
3469 Q_FOREACH(const TextEventAttachment &attachment, attachments) {
3470@@ -102,10 +104,11 @@
3471 MessageStatus messageStatus,
3472 const QDateTime &readTimestamp,
3473 const QString &subject,
3474+ InformationType informationType,
3475 const TextEventAttachments &attachments,
3476 const Participants &participants)
3477 : Event(*new TextEventPrivate(accountId, threadId, eventId, sender, timestamp, newEvent,
3478- message, messageType, messageStatus, readTimestamp, subject,
3479+ message, messageType, messageStatus, readTimestamp, subject, informationType,
3480 attachments, participants))
3481 {
3482 qDBusRegisterMetaType<QList<QVariantMap> >();
3483@@ -158,6 +161,12 @@
3484 return d->subject;
3485 }
3486
3487+InformationType TextEvent::informationType() const
3488+{
3489+ Q_D(const TextEvent);
3490+ return d->informationType;
3491+}
3492+
3493 TextEventAttachments TextEvent::attachments() const
3494 {
3495 Q_D(const TextEvent);
3496@@ -180,6 +189,7 @@
3497 Participants participants = Participants::fromVariant(properties[FieldParticipants]);
3498 QString message = properties[FieldMessage].toString();
3499 QString subject = properties[FieldSubject].toString();
3500+ InformationType informationType = (InformationType) properties[FieldInformationType].toInt();
3501 MessageType messageType = (MessageType) properties[FieldMessageType].toInt();
3502 MessageStatus messageStatus = (MessageStatus) properties[FieldMessageStatus].toInt();
3503 QDateTime readTimestamp = QDateTime::fromString(properties[FieldReadTimestamp].toString(), Qt::ISODate);
3504@@ -203,7 +213,7 @@
3505
3506 // and finally create the event
3507 event = TextEvent(accountId, threadId, eventId, senderId, timestamp, newEvent,
3508- message, messageType, messageStatus, readTimestamp, subject, attachments, participants);
3509+ message, messageType, messageStatus, readTimestamp, subject, informationType, attachments, participants);
3510 return event;
3511 }
3512
3513
3514=== modified file 'src/textevent.h'
3515--- src/textevent.h 2015-09-28 23:13:21 +0000
3516+++ src/textevent.h 2016-11-24 12:52:24 +0000
3517@@ -49,6 +49,7 @@
3518 MessageStatus messageStatus = MessageStatusUnknown,
3519 const QDateTime &readTimestamp = QDateTime(),
3520 const QString &subject = QString(),
3521+ InformationType informationType = InformationTypeNone,
3522 const TextEventAttachments &attachments = TextEventAttachments(),
3523 const Participants &participants = Participants());
3524
3525@@ -65,6 +66,7 @@
3526 QDateTime readTimestamp() const;
3527 void setReadTimestamp(const QDateTime &value);
3528 QString subject() const;
3529+ InformationType informationType() const;
3530 TextEventAttachments attachments() const;
3531
3532 static Event fromProperties(const QVariantMap &properties);
3533
3534=== modified file 'src/textevent_p.h'
3535--- src/textevent_p.h 2015-09-28 23:13:21 +0000
3536+++ src/textevent_p.h 2016-11-24 12:52:24 +0000
3537@@ -45,6 +45,7 @@
3538 MessageStatus theMessageStatus,
3539 const QDateTime &theReadTimestamp,
3540 const QString &theSubject,
3541+ InformationType theInformationType,
3542 const TextEventAttachments &theAttachments,
3543 const Participants &theParticipants);
3544 ~TextEventPrivate();
3545@@ -53,6 +54,7 @@
3546 MessageStatus messageStatus;
3547 QDateTime readTimestamp;
3548 QString subject;
3549+ InformationType informationType;
3550 TextEventAttachments attachments;
3551
3552 EventType type() const;
3553
3554=== modified file 'src/thread.cpp'
3555--- src/thread.cpp 2015-10-08 19:35:40 +0000
3556+++ src/thread.cpp 2016-11-24 12:52:24 +0000
3557@@ -1,5 +1,5 @@
3558 /*
3559- * Copyright (C) 2013-2015 Canonical, Ltd.
3560+ * Copyright (C) 2013-2016 Canonical, Ltd.
3561 *
3562 * Authors:
3563 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3564@@ -38,14 +38,18 @@
3565 }
3566
3567 ThreadPrivate::ThreadPrivate(const QString &theAccountId,
3568- const QString &theThreadId, EventType theType,
3569- const Participants &theParticipants,
3570- const Event &theLastEvent,
3571- int theCount,
3572- int theUnreadCount,
3573- const Threads &theGroupedThreads) :
3574- accountId(theAccountId), threadId(theThreadId), type(theType), participants(theParticipants),
3575- lastEvent(theLastEvent), count(theCount), unreadCount(theUnreadCount), groupedThreads(theGroupedThreads)
3576+ const QString &theThreadId, EventType theType,
3577+ const Participants &theParticipants,
3578+ const QDateTime &theTimestamp,
3579+ const Event &theLastEvent,
3580+ int theCount,
3581+ int theUnreadCount,
3582+ const Threads &theGroupedThreads,
3583+ ChatType theChatType,
3584+ const QVariantMap &theChatRoomInfo) :
3585+ accountId(theAccountId), threadId(theThreadId), type(theType), participants(theParticipants), timestamp(theTimestamp),
3586+ lastEvent(theLastEvent), count(theCount), unreadCount(theUnreadCount), groupedThreads(theGroupedThreads),
3587+ chatType(theChatType), chatRoomInfo(theChatRoomInfo)
3588 {
3589 }
3590
3591@@ -63,11 +67,14 @@
3592 Thread::Thread(const QString &accountId,
3593 const QString &threadId, EventType type,
3594 const Participants &participants,
3595+ const QDateTime &timestamp,
3596 const Event &lastEvent,
3597 int count,
3598 int unreadCount,
3599- const Threads &groupedThreads)
3600-: d_ptr(new ThreadPrivate(accountId, threadId, type, participants, lastEvent, count, unreadCount, groupedThreads))
3601+ const Threads &groupedThreads,
3602+ ChatType chatType,
3603+ const QVariantMap &chatRoomInfo)
3604+: d_ptr(new ThreadPrivate(accountId, threadId, type, participants, timestamp, lastEvent, count, unreadCount, groupedThreads, chatType, chatRoomInfo))
3605 {
3606 qDBusRegisterMetaType<QList<QVariantMap> >();
3607 qRegisterMetaType<QList<QVariantMap> >();
3608@@ -115,6 +122,12 @@
3609 return d->participants;
3610 }
3611
3612+QDateTime Thread::timestamp() const
3613+{
3614+ Q_D(const Thread);
3615+ return d->timestamp;
3616+}
3617+
3618 Event Thread::lastEvent() const
3619 {
3620 Q_D(const Thread);
3621@@ -139,6 +152,18 @@
3622 return d->groupedThreads;
3623 }
3624
3625+ChatType Thread::chatType() const
3626+{
3627+ Q_D(const Thread);
3628+ return d->chatType;
3629+}
3630+
3631+QVariantMap Thread::chatRoomInfo() const
3632+{
3633+ Q_D(const Thread);
3634+ return d->chatRoomInfo;
3635+}
3636+
3637 bool Thread::isNull() const
3638 {
3639 Q_D(const Thread);
3640@@ -182,11 +207,14 @@
3641 map[FieldAccountId] = d->accountId;
3642 map[FieldThreadId] = d->threadId;
3643 map[FieldType] = d->type;
3644+ map[FieldChatType] = d->chatType;
3645 map[FieldParticipants] = d->participants.toVariantList();
3646+ map[FieldTimestamp] = d->timestamp;
3647 map[FieldCount] = d->count;
3648 map[FieldUnreadCount] = d->unreadCount;
3649 map[FieldLastEventId] = lastEvent().eventId();
3650- map[FieldLastEventTimestamp] = lastEvent().timestamp();
3651+ map[FieldLastEventTimestamp] = d->timestamp;
3652+ map[FieldChatRoomInfo] = d->chatRoomInfo;
3653
3654 QList<QVariantMap> groupedThreads;
3655 Q_FOREACH(const Thread &thread, d->groupedThreads) {
3656@@ -210,8 +238,9 @@
3657 QString accountId = properties[FieldAccountId].toString();
3658 QString threadId = properties[FieldThreadId].toString();
3659 EventType type = (EventType) properties[FieldType].toInt();
3660-
3661+ ChatType chatType = (ChatType) properties[FieldChatType].toInt();
3662 Participants participants = Participants::fromVariant(properties[FieldParticipants]);
3663+ QDateTime timestamp = QDateTime::fromString(properties[FieldTimestamp].toString(), Qt::ISODate);
3664 int count = properties[FieldCount].toInt();
3665 int unreadCount = properties[FieldUnreadCount].toInt();
3666
3667@@ -227,6 +256,11 @@
3668 argument >> groupedThreads;
3669 }
3670 }
3671+ QVariantMap chatRoomInfo = qdbus_cast<QVariantMap>(properties[FieldChatRoomInfo]);
3672+ // dbus_cast fails if the map was generated by a qml app, so we demarshal it by hand
3673+ if (chatRoomInfo.isEmpty()) {
3674+ chatRoomInfo = properties[FieldChatRoomInfo].toMap();
3675+ }
3676
3677 Event event;
3678 switch (type) {
3679@@ -237,7 +271,7 @@
3680 event = VoiceEvent::fromProperties(properties);
3681 break;
3682 }
3683- return Thread(accountId, threadId, type, participants, event, count, unreadCount, groupedThreads);
3684+ return Thread(accountId, threadId, type, participants, timestamp, event, count, unreadCount, groupedThreads, chatType, chatRoomInfo);
3685 }
3686
3687 const QDBusArgument &operator>>(const QDBusArgument &argument, Threads &threads)
3688
3689=== modified file 'src/thread.h'
3690--- src/thread.h 2015-10-08 19:35:40 +0000
3691+++ src/thread.h 2016-11-24 12:52:24 +0000
3692@@ -1,5 +1,5 @@
3693 /*
3694- * Copyright (C) 2013-2015 Canonical, Ltd.
3695+ * Copyright (C) 2013-2016 Canonical, Ltd.
3696 *
3697 * Authors:
3698 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3699@@ -51,10 +51,13 @@
3700 const QString &threadId,
3701 EventType type,
3702 const Participants &participants,
3703+ const QDateTime &timestamp = QDateTime(),
3704 const Event &lastEvent = Event(),
3705 int count = 0,
3706 int unreadCount = 0,
3707- const Threads &groupedThreads = Threads());
3708+ const Threads &groupedThreads = Threads(),
3709+ ChatType chatType = ChatTypeNone,
3710+ const QVariantMap &chatRoomInfo = QVariantMap());
3711 Thread(const Thread &other);
3712 virtual ~Thread();
3713 Thread& operator=(const Thread &other);
3714@@ -63,10 +66,13 @@
3715 QString threadId() const;
3716 EventType type() const;
3717 Participants participants() const;
3718+ QDateTime timestamp() const;
3719 Event lastEvent() const;
3720 int count() const;
3721 int unreadCount() const;
3722+ ChatType chatType() const;
3723 Threads groupedThreads() const;
3724+ QVariantMap chatRoomInfo() const;
3725
3726 bool isNull() const;
3727 bool operator==(const Thread &other) const;
3728
3729=== modified file 'src/thread_p.h'
3730--- src/thread_p.h 2015-10-08 19:35:40 +0000
3731+++ src/thread_p.h 2016-11-24 12:52:24 +0000
3732@@ -1,5 +1,5 @@
3733 /*
3734- * Copyright (C) 2013-2015 Canonical, Ltd.
3735+ * Copyright (C) 2013-2016 Canonical, Ltd.
3736 *
3737 * Authors:
3738 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3739@@ -38,20 +38,26 @@
3740 const QString &theThreadId,
3741 EventType theType,
3742 const Participants &theParticipants,
3743+ const QDateTime &theTimestamp,
3744 const Event &theLastEvent,
3745 int theCount,
3746 int theUnreadCount,
3747- const Threads &theGroupedThreads);
3748+ const Threads &theGroupedThreads,
3749+ ChatType chatType,
3750+ const QVariantMap &chatRoomInfo);
3751 virtual ~ThreadPrivate();
3752
3753 QString accountId;
3754 QString threadId;
3755 Participants participants;
3756 EventType type;
3757+ QDateTime timestamp;
3758 Event lastEvent;
3759 int count;
3760 int unreadCount;
3761 Threads groupedThreads;
3762+ ChatType chatType;
3763+ QVariantMap chatRoomInfo;
3764 };
3765
3766 }
3767
3768=== modified file 'src/types.h'
3769--- src/types.h 2015-10-08 12:28:23 +0000
3770+++ src/types.h 2016-11-24 12:52:24 +0000
3771@@ -1,5 +1,5 @@
3772 /*
3773- * Copyright (C) 2013 Canonical, Ltd.
3774+ * Copyright (C) 2013-2016 Canonical, Ltd.
3775 *
3776 * Authors:
3777 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3778@@ -75,6 +75,32 @@
3779 MessageTypeInformation = 2
3780 };
3781
3782+enum InformationType
3783+{
3784+ InformationTypeNone = 0,
3785+ InformationTypeSimChange = 1,
3786+ InformationTypeText = 2,
3787+ InformationTypeSelfJoined = 3,
3788+ InformationTypeJoined = 4,
3789+ InformationTypeTitleChanged = 5,
3790+ InformationTypeInvitationSent = 6,
3791+ InformationTypeLeaving = 7,
3792+ InformationTypeSelfLeaving = 8,
3793+ InformationTypeAdminGranted = 9,
3794+ InformationTypeAdminRemoved = 10,
3795+ InformationTypeSelfAdminGranted = 11,
3796+ InformationTypeSelfAdminRemoved = 12,
3797+ InformationTypeSelfKicked = 13,
3798+ InformationTypeGroupGone = 14
3799+};
3800+
3801+enum ChatType
3802+{
3803+ ChatTypeNone = 0,
3804+ ChatTypeContact = 1,
3805+ ChatTypeRoom = 2
3806+};
3807+
3808 // FIXME (boiko): I think this needs to be changed to a simple enum and not flags,
3809 // as the statuses are mutually exclusive
3810 enum AttachmentFlag
3811@@ -86,6 +112,20 @@
3812
3813 Q_DECLARE_FLAGS(AttachmentFlags, AttachmentFlag)
3814
3815+enum ParticipantState
3816+{
3817+ ParticipantStateRegular = 0,
3818+ ParticipantStateLocalPending = 1,
3819+ ParticipantStateRemotePending = 2
3820+};
3821+
3822+enum ParticipantRoles
3823+{
3824+ ParticipantRoleNone = 0,
3825+ ParticipantRoleMember = 1,
3826+ ParticipantRoleAdmin = 2
3827+};
3828+
3829 // Event writing results
3830 enum EventWriteResult {
3831 EventWriteCreated,
3832@@ -106,12 +146,35 @@
3833 static const char* FieldEventId = "eventId";
3834 static const char* FieldType = "type";
3835 static const char* FieldParticipants = "participants";
3836+static const char* FieldParticipantIds = "participantIds";
3837 static const char* FieldCount = "count";
3838 static const char* FieldUnreadCount = "unreadCount";
3839 static const char* FieldSenderId = "senderId";
3840 static const char* FieldTimestamp = "timestamp";
3841 static const char* FieldDate = "date";
3842 static const char* FieldNewEvent = "newEvent";
3843+static const char* FieldChatType = "chatType";
3844+static const char* FieldChatRoomInfo = "chatRoomInfo";
3845+static const char* FieldChatRoomJoined = "joined";
3846+static const char* FieldChatRoomSelfRoles = "selfRoles";
3847+
3848+// Chat Room Info Fields
3849+static const char* FieldChatRoomName = "roomName";
3850+static const char* FieldChatRoomServer = "server";
3851+static const char* FieldChatRoomCreator = "creator";
3852+static const char* FieldChatRoomCreationTimestamp = "creationTimestamp";
3853+static const char* FieldChatRoomAnonymous = "anonymous";
3854+static const char* FieldChatRoomInviteOnly = "inviteOnly";
3855+static const char* FieldChatRoomParticipantLimit = "participantLimit";
3856+static const char* FieldChatRoomModerated = "moderated";
3857+static const char* FieldChatRoomTitle = "title";
3858+static const char* FieldChatRoomDescription = "description";
3859+static const char* FieldChatRoomPersistent = "persistent";
3860+static const char* FieldChatRoomPrivate = "private";
3861+static const char* FieldChatRoomPasswordProtected = "passwordProtected";
3862+static const char* FieldChatRoomPassword = "password";
3863+static const char* FieldChatRoomPasswordHint = "passwordHint";
3864+static const char* FieldChatRoomCanUpdateConfiguration = "canUpdateConfiguration";
3865
3866 // thread fields
3867 static const char* FieldLastEventId = "lastEventId";
3868@@ -124,6 +187,7 @@
3869 static const char* FieldMessageStatus = "messageStatus";
3870 static const char* FieldReadTimestamp = "readTimestamp";
3871 static const char* FieldSubject = "subject";
3872+static const char* FieldInformationType = "informationType";
3873 static const char* FieldAttachments = "attachments";
3874
3875 // text attachment fields
3876@@ -157,6 +221,8 @@
3877 static const char* FieldAvatar = "avatar";
3878 static const char* FieldIdentifier = "identifier";
3879 static const char* FieldDetailProperties = "detailProperties";
3880+static const char* FieldParticipantState = "state";
3881+static const char* FieldParticipantRoles = "roles";
3882
3883 }
3884
3885
3886=== modified file 'src/utils.cpp'
3887--- src/utils.cpp 2015-10-08 19:36:57 +0000
3888+++ src/utils.cpp 2016-11-24 12:52:24 +0000
3889@@ -1,5 +1,5 @@
3890 /*
3891- * Copyright (C) 2015 Canonical, Ltd.
3892+ * Copyright (C) 2015-2016 Canonical, Ltd.
3893 *
3894 * Authors:
3895 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3896@@ -19,10 +19,14 @@
3897 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3898 */
3899
3900+#include <unistd.h>
3901 #include "utils_p.h"
3902 #include "phoneutils_p.h"
3903 #include <QDebug>
3904 #include <QStringList>
3905+#include <QDBusInterface>
3906+#include <QDBusConnection>
3907+#include <QDBusReply>
3908 #include <QMap>
3909
3910 namespace History {
3911@@ -32,9 +36,12 @@
3912 }
3913
3914 // FIXME: find a better way to determine when accounts should be grouped
3915-bool Utils::shouldGroupAccount(const QString &accountId)
3916+bool Utils::shouldGroupThread(const Thread &thread)
3917 {
3918- return (matchFlagsForAccount(accountId) & MatchPhoneNumber);
3919+ if (protocolFromAccountId(thread.accountId()) == "multimedia") {
3920+ return thread.chatType() != History::ChatTypeRoom;
3921+ }
3922+ return (matchFlagsForAccount(thread.accountId()) & MatchPhoneNumber);
3923 }
3924
3925 MatchFlags Utils::matchFlagsForAccount(const QString &accountId)
3926@@ -150,4 +157,22 @@
3927 return normalizedId;
3928 }
3929
3930+QVariant Utils::getUserValue(const QString &interface, const QString &propName)
3931+{
3932+ QString uid = QString::number(getuid());
3933+ QString activeUser = "/org/freedesktop/Accounts/User" + uid;
3934+
3935+ QDBusInterface iface("org.freedesktop.Accounts",
3936+ activeUser,
3937+ "org.freedesktop.DBus.Properties",
3938+ QDBusConnection::systemBus());
3939+ QDBusReply<QVariant> reply = iface.call("Get", interface, propName);
3940+ if (reply.isValid()) {
3941+ return reply.value();
3942+ } else {
3943+ qWarning() << "Failed to get user property " << propName << " from AccountsService:" << reply.error().message();
3944+ }
3945+ return QVariant();
3946+}
3947+
3948 }
3949
3950=== modified file 'src/utils_p.h'
3951--- src/utils_p.h 2015-10-02 18:28:47 +0000
3952+++ src/utils_p.h 2016-11-24 12:52:24 +0000
3953@@ -1,5 +1,5 @@
3954 /*
3955- * Copyright (C) 2015 Canonical, Ltd.
3956+ * Copyright (C) 2015-2016 Canonical, Ltd.
3957 *
3958 * Authors:
3959 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3960@@ -23,6 +23,7 @@
3961 #define UTILS_P_H
3962
3963 #include "types.h"
3964+#include "thread.h"
3965
3966 namespace History {
3967
3968@@ -34,8 +35,9 @@
3969 static bool compareIds(const QString &accountId, const QString &id1, const QString & id2);
3970 static bool compareParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
3971 static bool compareNormalizedParticipants(const QStringList &participants1, const QStringList &participants2, MatchFlags flags);
3972- static bool shouldGroupAccount(const QString &accountId);
3973+ static bool shouldGroupThread(const Thread &thread);
3974 static QString normalizeId(const QString &accountId, const QString &id);
3975+ static QVariant getUserValue(const QString &interface, const QString &propName);
3976
3977 private:
3978 Utils();
3979
3980=== modified file 'tests/Ubuntu.History/HistoryEventModelTest.cpp'
3981--- tests/Ubuntu.History/HistoryEventModelTest.cpp 2016-03-30 16:53:04 +0000
3982+++ tests/Ubuntu.History/HistoryEventModelTest.cpp 2016-11-24 12:52:24 +0000
3983@@ -68,6 +68,7 @@
3984 History::MessageStatusRead,
3985 QDateTime::currentDateTime(),
3986 "The subject",
3987+ History::InformationTypeNone,
3988 History::TextEventAttachments(),
3989 textThread.participants());
3990 QVERIFY(mManager->writeEvents(History::Events() << event));
3991
3992=== modified file 'tests/Ubuntu.History/HistoryGroupedThreadsModelTest.cpp'
3993--- tests/Ubuntu.History/HistoryGroupedThreadsModelTest.cpp 2015-10-05 22:54:47 +0000
3994+++ tests/Ubuntu.History/HistoryGroupedThreadsModelTest.cpp 2016-11-24 12:52:24 +0000
3995@@ -123,7 +123,7 @@
3996
3997 // insert another event in second thread
3998 History::TextEvent secondEvent = History::TextEvent(textThread.accountId(), textThread.threadId(), QString("eventId2%1").arg(QString::number(qrand() % 1024)),
3999- QString("1234567"), QDateTime::currentDateTime(), false, "Random Message2",
4000+ QString("1234567"), QDateTime::currentDateTime().addSecs(1), false, "Random Message2",
4001 History::MessageTypeText);
4002 mManager->writeEvents(History::Events() << secondEvent);
4003 QTRY_COMPARE(dataChanged.count(), 1);
4004
4005=== modified file 'tests/common/mock/CMakeLists.txt'
4006--- tests/common/mock/CMakeLists.txt 2015-04-07 14:40:58 +0000
4007+++ tests/common/mock/CMakeLists.txt 2016-11-24 12:52:24 +0000
4008@@ -16,4 +16,4 @@
4009
4010 add_executable(telepathy-mock ${mock_SRCS})
4011 qt5_use_modules(telepathy-mock Core DBus)
4012-target_link_libraries(telepathy-mock ${TP_QT5_LIBRARIES} ${TELEPATHY_QT5_SERVICE_LIBRARIES} ${OFONO_QT_LIBRARIES} ${PULSEAUDIO_LIBRARIES})
4013+target_link_libraries(telepathy-mock ${TP_QT5_LIBRARIES} ${TELEPATHY_QT5_SERVICE_LIBRARIES} ${OFONO_QT_LIBRARIES} ${PULSEAUDIO_LIBRARIES} ${Qt5Network_LIBRARIES})
4014
4015=== modified file 'tests/common/mock/callchannel.cpp'
4016--- tests/common/mock/callchannel.cpp 2015-06-16 16:16:54 +0000
4017+++ tests/common/mock/callchannel.cpp 2016-11-24 12:52:24 +0000
4018@@ -194,7 +194,8 @@
4019 reason.reason = Tp::CallStateChangeReasonNoAnswer;
4020 }
4021 mCallChannel->setCallState(Tp::CallStateEnded, 0, reason, stateDetails);
4022- mBaseChannel->close();
4023+ // leave the channel opened for a bit longer so that the call state gets propagated correctly.
4024+ QTimer::singleShot(10, mBaseChannel.data(), &Tp::BaseChannel::close);
4025 } else if (state == "active") {
4026 qDebug() << "active";
4027 mHoldIface->setHoldState(Tp::LocalHoldStateUnheld, Tp::LocalHoldStateReasonNone);
4028
4029=== modified file 'tests/common/mock/mockconnectiondbus.cpp'
4030--- tests/common/mock/mockconnectiondbus.cpp 2015-04-07 14:40:58 +0000
4031+++ tests/common/mock/mockconnectiondbus.cpp 2016-11-24 12:52:24 +0000
4032@@ -70,16 +70,15 @@
4033
4034 bool MockConnectionDBus::connectToBus()
4035 {
4036- bool ok = QDBusConnection::sessionBus().registerService("com.canonical.MockConnection");
4037- if (!ok) {
4038- return false;
4039- }
4040-
4041 if (!mAdaptor) {
4042 mAdaptor = new MockConnectionAdaptor(this);
4043 }
4044
4045- return QDBusConnection::sessionBus().registerObject(mObjectPath, this);
4046+ if (!QDBusConnection::sessionBus().registerObject(mObjectPath, this)) {
4047+ return false;
4048+ }
4049+
4050+ return QDBusConnection::sessionBus().registerService("com.canonical.MockConnection");
4051 }
4052
4053 void MockConnectionDBus::PlaceIncomingMessage(const QString &message, const QVariantMap &properties)
4054
4055=== modified file 'tests/common/mock/textchannel.cpp'
4056--- tests/common/mock/textchannel.cpp 2015-08-28 10:15:47 +0000
4057+++ tests/common/mock/textchannel.cpp 2016-11-24 12:52:24 +0000
4058@@ -74,7 +74,9 @@
4059 baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMessagesIface));
4060
4061 // group stuff
4062- mGroupIface = Tp::BaseChannelGroupInterface::create(Tp::ChannelGroupFlagCanAdd, conn->selfHandle());
4063+ mGroupIface = Tp::BaseChannelGroupInterface::create();
4064+ mGroupIface->setGroupFlags(Tp::ChannelGroupFlagCanAdd);
4065+ mGroupIface->setSelfHandle(conn->selfHandle());
4066 mGroupIface->setAddMembersCallback(Tp::memFun(this,&MockTextChannel::onAddMembers));
4067 mGroupIface->setRemoveMembersCallback(Tp::memFun(this,&MockTextChannel::onRemoveMembers));
4068 baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mGroupIface));
4069@@ -232,7 +234,7 @@
4070 mMembers << handle;
4071 }
4072 }
4073- mGroupIface->addMembers(handles, recipients);
4074+ mGroupIface->setMembers(mMembers, QVariantMap());
4075 }
4076
4077 QStringList MockTextChannel::recipients() const
4078@@ -250,7 +252,7 @@
4079 addMembers(mConnection->inspectHandles(Tp::HandleTypeContact, handles, error));
4080 }
4081
4082-void MockTextChannel::onRemoveMembers(const Tp::UIntList &handles, const QString &message, Tp::DBusError *error)
4083+void MockTextChannel::onRemoveMembers(const Tp::UIntList &handles, const QString &message, uint reason, Tp::DBusError *error)
4084 {
4085 Q_FOREACH(uint handle, handles) {
4086 Q_FOREACH(const QString &recipient, mConnection->inspectHandles(Tp::HandleTypeContact, Tp::UIntList() << handle, error)) {
4087@@ -258,5 +260,5 @@
4088 }
4089 mMembers.removeAll(handle);
4090 }
4091- mGroupIface->removeMembers(handles);
4092+ mGroupIface->setMembers(mMembers, QVariantMap());
4093 }
4094
4095=== modified file 'tests/common/mock/textchannel.h'
4096--- tests/common/mock/textchannel.h 2015-04-07 14:40:58 +0000
4097+++ tests/common/mock/textchannel.h 2016-11-24 12:52:24 +0000
4098@@ -45,8 +45,8 @@
4099 void addMembers(QStringList recipients);
4100 QStringList recipients() const;
4101 Tp::UIntList members();
4102+ void onRemoveMembers(const Tp::UIntList& handles, const QString& message, uint reason, Tp::DBusError* error);
4103 void onAddMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error);
4104- void onRemoveMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error);
4105
4106 public Q_SLOTS:
4107 void placeDeliveryReport(const QString &messageId, const QString &status);
4108
4109=== modified file 'tests/daemon/CMakeLists.txt'
4110--- tests/daemon/CMakeLists.txt 2016-09-09 14:48:33 +0000
4111+++ tests/daemon/CMakeLists.txt 2016-11-24 12:52:24 +0000
4112@@ -5,7 +5,7 @@
4113 ${TP_QT5_INCLUDE_DIRS}
4114 )
4115
4116-#generate_telepathy_test(DaemonTest
4117-# SOURCES DaemonTest.cpp handler.cpp approver.cpp
4118-# TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon
4119-# WAIT_FOR com.canonical.HistoryService)
4120+generate_telepathy_test(DaemonTest
4121+ SOURCES DaemonTest.cpp handler.cpp approver.cpp
4122+ TASKS --task ${CMAKE_BINARY_DIR}/daemon/history-daemon --ignore-return --task-name history-daemon
4123+ WAIT_FOR com.canonical.HistoryService)
4124
4125=== modified file 'tests/daemon/DaemonTest.cpp'
4126--- tests/daemon/DaemonTest.cpp 2015-10-06 12:50:14 +0000
4127+++ tests/daemon/DaemonTest.cpp 2016-11-24 12:52:24 +0000
4128@@ -324,7 +324,7 @@
4129 connect(mAccount->connection()->contactManager()->contactsForIdentifiers(QStringList() << phoneNumber),
4130 SIGNAL(finished(Tp::PendingOperation*)),
4131 SLOT(onPendingContactsFinished(Tp::PendingOperation*)));
4132- QTRY_COMPARE(spy.count(), 1);
4133+ QVERIFY(spy.wait());
4134
4135 QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
4136 QCOMPARE(contacts.count(), 1);
4137@@ -335,7 +335,7 @@
4138 Q_FOREACH(Tp::ContactPtr contact, contacts) {
4139 mAccount->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".HistoryTestHandler");
4140 }
4141- QTRY_COMPARE(spyCallChannel.count(), 1);
4142+ QVERIFY(spyCallChannel.wait());
4143
4144 Tp::CallChannelPtr channel = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
4145 QVERIFY(channel);
4146
4147=== modified file 'tests/libhistoryservice/ParticipantTest.cpp'
4148--- tests/libhistoryservice/ParticipantTest.cpp 2015-10-05 22:20:00 +0000
4149+++ tests/libhistoryservice/ParticipantTest.cpp 2016-11-24 12:52:24 +0000
4150@@ -58,16 +58,20 @@
4151 QString contactId("theContactId");
4152 QString alias("theAlias");
4153 QString avatar("theAvatar");
4154+ uint state = History::ParticipantStateRegular;
4155+ uint roles = History::ParticipantRoleMember;
4156 QVariantMap detailProperties;
4157 detailProperties["someProperty"] = "someValue";
4158
4159
4160- History::Participant participant(accountId, identifier, contactId, alias, avatar, detailProperties);
4161+ History::Participant participant(accountId, identifier, contactId, alias, avatar, state, roles, detailProperties);
4162 QCOMPARE(participant.accountId(), accountId);
4163 QCOMPARE(participant.identifier(), identifier);
4164 QCOMPARE(participant.contactId(), contactId);
4165 QCOMPARE(participant.alias(), alias);
4166 QCOMPARE(participant.avatar(), avatar);
4167+ QCOMPARE(participant.state(), state);
4168+ QCOMPARE(participant.roles(), roles);
4169 QCOMPARE(participant.detailProperties(), detailProperties);
4170 }
4171
4172@@ -105,7 +109,7 @@
4173 {
4174 QVariantMap detailProperties;
4175 detailProperties["theProperty"] = "theValue";
4176- History::Participant original("accountId", "identifier", "contactId", "alias", "avatar", detailProperties);
4177+ History::Participant original("accountId", "identifier", "contactId", "alias", "avatar", History::ParticipantStateRegular, History::ParticipantRoleAdmin, detailProperties);
4178
4179 History::Participant copy(original);
4180
4181@@ -114,6 +118,8 @@
4182 QCOMPARE(copy.contactId(), original.contactId());
4183 QCOMPARE(copy.alias(), original.alias());
4184 QCOMPARE(copy.avatar(), original.avatar());
4185+ QCOMPARE(copy.state(), original.state());
4186+ QCOMPARE(copy.roles(), original.roles());
4187 QCOMPARE(copy.detailProperties(), original.detailProperties());
4188 }
4189
4190@@ -121,7 +127,7 @@
4191 {
4192 QVariantMap detailProperties;
4193 detailProperties["theProperty2"] = "theValue2";
4194- History::Participant original("accountId2", "identifier2", "contactId2", "alias2", "avatar2", detailProperties);
4195+ History::Participant original("accountId2", "identifier2", "contactId2", "alias2", "avatar2", History::ParticipantStateRegular, History::ParticipantRoleMember, detailProperties);
4196
4197 History::Participant copy;
4198 copy = original;
4199@@ -131,6 +137,8 @@
4200 QCOMPARE(copy.contactId(), original.contactId());
4201 QCOMPARE(copy.alias(), original.alias());
4202 QCOMPARE(copy.avatar(), original.avatar());
4203+ QCOMPARE(copy.state(), original.state());
4204+ QCOMPARE(copy.roles(), original.roles());
4205 QCOMPARE(copy.detailProperties(), original.detailProperties());
4206 }
4207
4208@@ -166,13 +174,15 @@
4209 QVariantMap detailProperties;
4210 detailProperties["someDetailProperty"] = "someValue";
4211
4212- History::Participant participant("theAccountId", "theIdentifier", "theContactId", "theAlias", "theAvatar", detailProperties);
4213+ History::Participant participant("theAccountId", "theIdentifier", "theContactId", "theAlias", "theAvatar", History::ParticipantStateRegular, History::ParticipantRoleAdmin, detailProperties);
4214 QVariantMap properties = participant.properties();
4215 QCOMPARE(properties[History::FieldAccountId].toString(), participant.accountId());
4216 QCOMPARE(properties[History::FieldIdentifier].toString(), participant.identifier());
4217 QCOMPARE(properties[History::FieldContactId].toString(), participant.contactId());
4218 QCOMPARE(properties[History::FieldAlias].toString(), participant.alias());
4219 QCOMPARE(properties[History::FieldAvatar].toString(), participant.avatar());
4220+ QCOMPARE(properties[History::FieldParticipantState].toUInt(), participant.state());
4221+ QCOMPARE(properties[History::FieldParticipantRoles].toUInt(), participant.roles());
4222 QCOMPARE(properties[History::FieldDetailProperties].toMap(), participant.detailProperties());
4223 }
4224
4225@@ -186,6 +196,8 @@
4226 properties[History::FieldContactId] = "someContactId";
4227 properties[History::FieldAlias] = "someAlias";
4228 properties[History::FieldAvatar] = "someAvatar";
4229+ properties[History::FieldParticipantState] = History::ParticipantStateRegular;
4230+ properties[History::FieldParticipantRoles] = History::ParticipantRoleAdmin;
4231 detailProperties["someDetailProperty"] = "someValue";
4232 properties[History::FieldDetailProperties] = detailProperties;
4233
4234@@ -195,6 +207,8 @@
4235 QCOMPARE(participant.contactId(), properties[History::FieldContactId].toString());
4236 QCOMPARE(participant.alias(), properties[History::FieldAlias].toString());
4237 QCOMPARE(participant.avatar(), properties[History::FieldAvatar].toString());
4238+ QCOMPARE(participant.state(), properties[History::FieldParticipantState].toUInt());
4239+ QCOMPARE(participant.roles(), properties[History::FieldParticipantRoles].toUInt());
4240 QCOMPARE(participant.detailProperties(), properties[History::FieldDetailProperties].toMap());
4241 }
4242
4243
4244=== modified file 'tests/libhistoryservice/TextEventTest.cpp'
4245--- tests/libhistoryservice/TextEventTest.cpp 2015-09-28 23:13:21 +0000
4246+++ tests/libhistoryservice/TextEventTest.cpp 2016-11-24 12:52:24 +0000
4247@@ -53,33 +53,34 @@
4248 QTest::addColumn<int>("messageStatus");
4249 QTest::addColumn<QDateTime>("readTimestamp");
4250 QTest::addColumn<QString>("subject");
4251+ QTest::addColumn<int>("informationType");
4252 QTest::addColumn<QStringList>("participants");
4253
4254 QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId"
4255 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4256 << true << "One Test Message" << (int)History::MessageTypeText
4257- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject"
4258+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (int) History::InformationTypeJoined
4259 << (QStringList() << "testParticipant");
4260 QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2"
4261 << "testSenderId2" << QDateTime::currentDateTime().addDays(-10)
4262 << false << "One Test Message" << (int)History::MessageTypeText
4263- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2"
4264+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (int) History::InformationTypeLeaving
4265 << (QStringList() << "testParticipant2");
4266 QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId"
4267 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4268 << true << "One Test Message" << (int)History::MessageTypeText
4269 << (int)History::MessageStatusAccepted
4270- << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3"
4271+ << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (int) History::InformationTypeSelfLeaving
4272 << (QStringList() << "testParticipant");
4273 QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId"
4274 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4275 << true << "One Test Message" << (int)History::MessageTypeMultiPart
4276- << 0 << QDateTime::currentDateTime().addDays(-5) << QString()
4277+ << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (int) History::InformationTypeNone
4278 << (QStringList() << "testParticipant");
4279 QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2"
4280 << "testSenderId2" << QDateTime::currentDateTime().addDays(-7)
4281 << true << "One Test Message 2" << (int)History::MessageTypeText
4282- << 0 << QDateTime::currentDateTime().addDays(-4) << QString()
4283+ << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (int) History::InformationTypeNone
4284 << (QStringList() << "one" << "two" << "three" << "four");
4285 }
4286
4287@@ -96,10 +97,11 @@
4288 QFETCH(int, messageStatus);
4289 QFETCH(QDateTime, readTimestamp);
4290 QFETCH(QString, subject);
4291+ QFETCH(int, informationType);
4292 QFETCH(QStringList, participants);
4293 History::TextEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent,
4294 message, (History::MessageType)messageType, (History::MessageStatus)messageStatus,
4295- readTimestamp, subject, History::TextEventAttachments(),
4296+ readTimestamp, subject, (History::InformationType) informationType, History::TextEventAttachments(),
4297 participantsFromIdentifiers(accountId, participants));
4298
4299 // check that the values are properly set
4300@@ -150,33 +152,34 @@
4301 QTest::addColumn<int>("messageStatus");
4302 QTest::addColumn<QDateTime>("readTimestamp");
4303 QTest::addColumn<QString>("subject");
4304+ QTest::addColumn<int>("informationType");
4305 QTest::addColumn<QStringList>("participants");
4306
4307 QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId"
4308 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4309 << true << "One Test Message" << (int)History::MessageTypeText
4310- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject"
4311+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (int) History::InformationTypeJoined
4312 << (QStringList() << "testParticipant");
4313 QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2"
4314 << "testSenderId2" << QDateTime::currentDateTime().addDays(-10)
4315 << false << "One Test Message" << (int)History::MessageTypeText
4316- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2"
4317+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (int) History::InformationTypeNone
4318 << (QStringList() << "testParticipant2");
4319 QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId"
4320 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4321 << true << "One Test Message" << (int)History::MessageTypeText
4322 << (int)History::MessageStatusAccepted
4323- << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3"
4324+ << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (int) History::InformationTypeNone
4325 << (QStringList() << "testParticipant");
4326 QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId"
4327 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4328 << true << "One Test Message" << (int)History::MessageTypeMultiPart
4329- << 0 << QDateTime::currentDateTime().addDays(-5) << QString()
4330+ << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (int) History::InformationTypeNone
4331 << (QStringList() << "testParticipant");
4332 QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2"
4333 << "testSenderId2" << QDateTime::currentDateTime().addDays(-7)
4334 << true << "One Test Message 2" << (int)History::MessageTypeText
4335- << 0 << QDateTime::currentDateTime().addDays(-4) << QString()
4336+ << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (int) History::InformationTypeNone
4337 << (QStringList() << "one" << "two" << "three" << "four");
4338 }
4339
4340@@ -193,6 +196,7 @@
4341 QFETCH(int, messageStatus);
4342 QFETCH(QDateTime, readTimestamp);
4343 QFETCH(QString, subject);
4344+ QFETCH(int, informationType);
4345 QFETCH(QStringList, participants);
4346
4347 QVariantMap properties;
4348@@ -207,6 +211,7 @@
4349 properties[History::FieldMessageStatus] = messageStatus;
4350 properties[History::FieldReadTimestamp] = readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz");
4351 properties[History::FieldSubject] = subject;
4352+ properties[History::FieldInformationType] = informationType;
4353 properties[History::FieldParticipants] = participantsFromIdentifiers(accountId, participants).toVariantList();
4354
4355 History::TextEvent textEvent = History::TextEvent::fromProperties(properties);
4356@@ -221,6 +226,7 @@
4357 QCOMPARE(textEvent.messageStatus(), (History::MessageStatus) messageStatus);
4358 QCOMPARE(textEvent.readTimestamp().toString(Qt::ISODate), readTimestamp.toString(Qt::ISODate));
4359 QCOMPARE(textEvent.subject(), subject);
4360+ QCOMPARE(textEvent.informationType(), (History::InformationType) informationType);
4361 QCOMPARE(textEvent.participants().identifiers(), participants);
4362 }
4363
4364@@ -245,33 +251,34 @@
4365 QTest::addColumn<int>("messageStatus");
4366 QTest::addColumn<QDateTime>("readTimestamp");
4367 QTest::addColumn<QString>("subject");
4368+ QTest::addColumn<int>("informationType");
4369 QTest::addColumn<QStringList>("participants");
4370
4371 QTest::newRow("unread message") << "testAccountId" << "testThreadId" << "testEventId"
4372 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4373 << true << "One Test Message" << (int)History::MessageTypeText
4374- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject"
4375+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject" << (int) History::InformationTypeNone
4376 << (QStringList() << "testParticipant");
4377 QTest::newRow("read message") << "testAccountId2" << "testThreadId2" << "testEventId2"
4378 << "testSenderId2" << QDateTime::currentDateTime().addDays(-10)
4379 << false << "One Test Message" << (int)History::MessageTypeText
4380- << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2"
4381+ << 0 << QDateTime::currentDateTime().addDays(-5) << "Test Subject 2" << (int) History::InformationTypeNone
4382 << (QStringList() << "testParticipant2");
4383 QTest::newRow("message status") << "testAccountId" << "testThreadId" << "testEventId"
4384 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4385 << true << "One Test Message" << (int)History::MessageTypeText
4386 << (int)History::MessageStatusAccepted
4387- << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3"
4388+ << QDateTime::currentDateTime().addDays(-5) << "Test Subject 3" << (int) History::InformationTypeNone
4389 << (QStringList() << "testParticipant");
4390 QTest::newRow("multi party message") << "testAccountId" << "testThreadId" << "testEventId"
4391 << "testSenderId" << QDateTime::currentDateTime().addDays(-10)
4392 << true << "One Test Message" << (int)History::MessageTypeMultiPart
4393- << 0 << QDateTime::currentDateTime().addDays(-5) << QString()
4394+ << 0 << QDateTime::currentDateTime().addDays(-5) << QString() << (int) History::InformationTypeNone
4395 << (QStringList() << "testParticipant");
4396 QTest::newRow("multiple participants") << "testAccountId2" << "testThreadId2" << "testEventId2"
4397 << "testSenderId2" << QDateTime::currentDateTime().addDays(-7)
4398 << true << "One Test Message 2" << (int)History::MessageTypeText
4399- << 0 << QDateTime::currentDateTime().addDays(-4) << QString()
4400+ << 0 << QDateTime::currentDateTime().addDays(-4) << QString() << (int) History::InformationTypeNone
4401 << (QStringList() << "one" << "two" << "three" << "four");
4402 }
4403
4404@@ -288,10 +295,11 @@
4405 QFETCH(int, messageStatus);
4406 QFETCH(QDateTime, readTimestamp);
4407 QFETCH(QString, subject);
4408+ QFETCH(int, informationType);
4409 QFETCH(QStringList, participants);
4410 History::TextEvent event(accountId, threadId, eventId, senderId, timestamp, newEvent,
4411 message, (History::MessageType)messageType, (History::MessageStatus)messageStatus,
4412- readTimestamp, subject, History::TextEventAttachments(),
4413+ readTimestamp, subject, History::InformationTypeNone, History::TextEventAttachments(),
4414 participantsFromIdentifiers(accountId, participants));
4415
4416 QVariantMap properties = event.properties();
4417@@ -306,6 +314,7 @@
4418 QCOMPARE(properties[History::FieldMessageStatus].toInt(), messageStatus);
4419 QCOMPARE(properties[History::FieldReadTimestamp].toString(), readTimestamp.toString("yyyy-MM-ddTHH:mm:ss.zzz"));
4420 QCOMPARE(properties[History::FieldSubject].toString(), subject);
4421+ QCOMPARE(properties[History::FieldInformationType].toInt(), informationType);
4422 QCOMPARE(History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers(), participants);
4423 }
4424
4425
4426=== modified file 'tests/libhistoryservice/ThreadTest.cpp'
4427--- tests/libhistoryservice/ThreadTest.cpp 2015-10-02 16:48:19 +0000
4428+++ tests/libhistoryservice/ThreadTest.cpp 2016-11-24 12:52:24 +0000
4429@@ -102,11 +102,12 @@
4430 break;
4431 }
4432
4433- History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event, count, unreadCount);
4434+ History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event.timestamp(), event, count, unreadCount);
4435 QCOMPARE(threadItem.accountId(), accountId);
4436 QCOMPARE(threadItem.threadId(), threadId);
4437 QCOMPARE(threadItem.type(), type);
4438 QCOMPARE(threadItem.participants().identifiers(), participants);
4439+ QCOMPARE(threadItem.timestamp(), event.timestamp());
4440 QCOMPARE(threadItem.lastEvent(), event);
4441 QCOMPARE(threadItem.count(), count);
4442 QCOMPARE(threadItem.unreadCount(), unreadCount);
4443@@ -172,6 +173,7 @@
4444 QCOMPARE(thread.threadId(), threadId);
4445 QCOMPARE(thread.type(), type);
4446 QCOMPARE(thread.participants().identifiers(), participants);
4447+ QCOMPARE(thread.timestamp(), event.timestamp());
4448 QCOMPARE(thread.count(), count);
4449 QCOMPARE(thread.unreadCount(), unreadCount);
4450 QVERIFY(thread.lastEvent() == event);
4451@@ -229,7 +231,7 @@
4452 break;
4453 }
4454
4455- History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event, count, unreadCount);
4456+ History::Thread threadItem(accountId, threadId, type, participantsFromIdentifiers(accountId, participants), event.timestamp(), event, count, unreadCount);
4457 QVariantMap properties = threadItem.properties();
4458 QCOMPARE(properties[History::FieldAccountId].toString(), accountId);
4459 QCOMPARE(properties[History::FieldThreadId].toString(), threadId);
4460
4461=== modified file 'tests/plugins/sqlite/SqlitePluginTest.cpp'
4462--- tests/plugins/sqlite/SqlitePluginTest.cpp 2015-09-23 22:27:38 +0000
4463+++ tests/plugins/sqlite/SqlitePluginTest.cpp 2016-11-24 12:52:24 +0000
4464@@ -249,36 +249,35 @@
4465 {
4466 // clear the database
4467 SQLiteDatabase::instance()->reopen();
4468+ QSqlQuery query(SQLiteDatabase::instance()->database());
4469
4470 QVERIFY(mPlugin->beginBatchOperation());
4471- mPlugin->createThreadForParticipants("accountOne", History::EventTypeText, QStringList() << "participantOne");
4472- mPlugin->createThreadForParticipants("accountTwo", History::EventTypeText, QStringList() << "participantTwo");
4473- mPlugin->createThreadForParticipants("accountThree", History::EventTypeText, QStringList() << "participantThree");
4474+ QVERIFY(query.exec("UPDATE schema_version SET version=123"));
4475 QVERIFY(mPlugin->endBatchOperation());
4476
4477 // check that the data was actually written
4478- QSqlQuery query(SQLiteDatabase::instance()->database());
4479- QVERIFY(query.exec("SELECT count(*) FROM threads"));
4480+ QVERIFY(query.exec("SELECT version FROM schema_version"));
4481 QVERIFY(query.next());
4482- QCOMPARE(query.value(0).toInt(), 3);
4483+ QCOMPARE(query.value(0).toInt(), 123);
4484 }
4485
4486 void SqlitePluginTest::testRollback()
4487 {
4488 // clear the database
4489 SQLiteDatabase::instance()->reopen();
4490+ QSqlQuery query(SQLiteDatabase::instance()->database());
4491+ QVERIFY(query.exec("SELECT version FROM schema_version"));
4492+ QVERIFY(query.next());
4493+ int version = query.value(0).toInt();
4494
4495 QVERIFY(mPlugin->beginBatchOperation());
4496- mPlugin->createThreadForParticipants("accountOne", History::EventTypeText, QStringList() << "participantOne");
4497- mPlugin->createThreadForParticipants("accountTwo", History::EventTypeText, QStringList() << "participantTwo");
4498- mPlugin->createThreadForParticipants("accountThree", History::EventTypeText, QStringList() << "participantThree");
4499+ QVERIFY(query.exec("UPDATE schema_version SET version=255"));
4500 QVERIFY(mPlugin->rollbackBatchOperation());
4501
4502 // check that the steps were reverted
4503- QSqlQuery query(SQLiteDatabase::instance()->database());
4504- QVERIFY(query.exec("SELECT count(*) FROM threads"));
4505+ QVERIFY(query.exec("SELECT version FROM schema_version"));
4506 QVERIFY(query.next());
4507- QCOMPARE(query.value(0).toInt(), 0);
4508+ QCOMPARE(query.value(0).toInt(), version);
4509 }
4510
4511 void SqlitePluginTest::testQueryThreads()
4512@@ -319,7 +318,7 @@
4513 QTest::newRow("text event with attachments") << History::TextEvent("mmsAccountId", "mmsSender", "mmsEventId", "mmsSender",
4514 QDateTime::currentDateTime(), false, "Hello with attachments",
4515 History::MessageTypeMultiPart, History::MessageStatusDelivered,
4516- QDateTime::currentDateTime(), "The Subject", attachments).properties();
4517+ QDateTime::currentDateTime(), "The Subject", History::InformationTypeNone, attachments).properties();
4518 }
4519
4520 void SqlitePluginTest::testWriteTextEvent()
4521@@ -406,7 +405,7 @@
4522 thread[History::FieldEventId].toString(), "theAttachmentId", "text/plain", "/file/path");
4523 History::TextEvent textEvent(thread[History::FieldAccountId].toString(), thread[History::FieldThreadId].toString(), "theEventId",
4524 "theParticipant", QDateTime::currentDateTime(), true, "Hi there!", History::MessageTypeMultiPart,
4525- History::MessageStatusPending, QDateTime::currentDateTime(), "theSubject",
4526+ History::MessageStatusPending, QDateTime::currentDateTime(), "theSubject", History::InformationTypeNone,
4527 History::TextEventAttachments() << attachment);
4528 QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated);
4529
4530@@ -606,7 +605,7 @@
4531 QString("textEventId%1").arg(QString::number(i)), "textParticipant",
4532 QDateTime::currentDateTime(), true, "Hello World!", History::MessageTypeMultiPart,
4533 History::MessageStatusPending, QDateTime::currentDateTime(),
4534- "theSubject", History::TextEventAttachments() << attachment);
4535+ "theSubject", History::InformationTypeNone, History::TextEventAttachments() << attachment);
4536 QCOMPARE(mPlugin->writeTextEvent(textEvent.properties()), History::EventWriteCreated);
4537 }
4538
4539@@ -659,7 +658,7 @@
4540 QTest::newRow("text event with attachments") << History::TextEvent("mmsAccountId", "mmsSender", "mmsEventId", "mmsSender",
4541 QDateTime::currentDateTime(), false, "Hello with attachments",
4542 History::MessageTypeMultiPart, History::MessageStatusDelivered,
4543- QDateTime::currentDateTime(), "The Subject", attachments).properties();
4544+ QDateTime::currentDateTime(), "The Subject", History::InformationTypeNone, attachments).properties();
4545 QTest::newRow("missed call") << History::VoiceEvent("theAccountId", "theSenderId", "theEventId", "theSenderId",
4546 QDateTime::currentDateTime(), true, true).properties();
4547 QTest::newRow("incoming call") << History::VoiceEvent("otherAccountId", "otherSenderId", "otherEventId", "otherSenderId",

Subscribers

People subscribed via source and target branches

to all changes: