33
35
#include <QSqlError>
34
36
#include <QDBusMetaType>
38
QString generateThreadMapKey(const History::Thread &thread)
40
return thread.accountId() + thread.threadId();
43
QString generateThreadMapKey(const QString &accountId, const QString &threadId)
45
return accountId + threadId;
36
48
SQLiteHistoryPlugin::SQLiteHistoryPlugin(QObject *parent) :
49
QObject(parent), mInitialised(false)
39
51
// just trigger the database creation or update
40
52
SQLiteDatabase::instance();
54
updateGroupedThreadsCache();
59
void SQLiteHistoryPlugin::updateGroupedThreadsCache()
61
History::PluginThreadView *view = queryThreads(History::EventTypeText, History::Sort("timestamp", Qt::DescendingOrder), History::Filter());
62
QList<QVariantMap> threads;
63
while (view->IsValid()) {
64
QList<QVariantMap> page = view->NextPage();
65
if (page.size() > 0) {
71
addThreadsToCache(threads);
74
void SQLiteHistoryPlugin::addThreadsToCache(const QList<QVariantMap> &threads)
76
Q_FOREACH (const QVariantMap &properties, threads) {
77
History::Thread thread = History::Thread::fromProperties(properties);
78
const QString &threadKey = generateThreadMapKey(thread);
80
if (thread.type() != History::EventTypeText) {
82
} else if (!History::Utils::shouldGroupAccount(thread.accountId())) {
83
// never group non phone accounts
84
mConversationsCache[threadKey] = History::Threads() << thread;
85
mConversationsCacheKeys[threadKey] = threadKey;
88
// find conversation grouping this thread
89
if (mConversationsCacheKeys.contains(threadKey)) {
90
QString conversationKey = mConversationsCacheKeys[threadKey];
91
History::Threads groupedThreads = mConversationsCache[conversationKey];
92
Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
93
mConversationsCacheKeys.remove(generateThreadMapKey(groupedThread));
95
groupedThreads.removeAll(thread);
96
groupedThreads.append(thread);
97
mConversationsCache[conversationKey] = groupedThreads;
98
mConversationsCacheKeys.remove(threadKey);
99
updateDisplayedThread(conversationKey);
102
// if not found, we have to iterate the list and compare phone numbers
104
QMap<QString, History::Threads>::iterator it = mConversationsCache.begin();
105
while (it != mConversationsCache.end()) {
106
const QString &conversationKey = it.key();
107
History::Threads groupedThreads = it.value();
108
Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
109
found = History::Utils::compareNormalizedParticipants(thread.participants(), groupedThread.participants(), History::MatchPhoneNumber);
111
Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
112
mConversationsCacheKeys.remove(generateThreadMapKey(groupedThread));
114
mConversationsCache[conversationKey] += thread;
115
updateDisplayedThread(conversationKey);
125
mConversationsCache[threadKey] = History::Threads() << thread;
126
mConversationsCacheKeys[threadKey] = threadKey;
131
bool SQLiteHistoryPlugin::lessThan(const QVariantMap &left, const QVariantMap &right) const
133
QVariant leftValue = left[History::FieldLastEventTimestamp];
134
QVariant rightValue = right[History::FieldLastEventTimestamp];
136
return leftValue < rightValue;
139
void SQLiteHistoryPlugin::updateDisplayedThread(const QString &displayedThreadKey)
141
History::Threads threads = mConversationsCache[displayedThreadKey];
142
History::Thread displayedThread = threads.first();
143
QVariantMap displayedProperties = displayedThread.properties();
144
Q_FOREACH(const History::Thread &other, threads) {
145
if (lessThan(displayedProperties, other.properties())) {
146
displayedThread = other;
147
displayedProperties = displayedThread.properties();
151
QString newDisplayedThreadKey = generateThreadMapKey(displayedThread);
152
mConversationsCache.remove(displayedThreadKey);
153
mConversationsCache[newDisplayedThreadKey] = threads;
155
// update reverse threadId -> conversationId map
156
Q_FOREACH(const History::Thread &groupedThread, threads) {
157
mConversationsCacheKeys[generateThreadMapKey(groupedThread)] = newDisplayedThreadKey;
161
void SQLiteHistoryPlugin::removeThreadFromCache(const QVariantMap &properties)
163
History::Thread thread = History::Thread::fromProperties(properties);
164
QString threadKey = generateThreadMapKey(thread);
166
if (thread.type() != History::EventTypeText || !History::Utils::shouldGroupAccount(thread.accountId())) {
167
mConversationsCache.remove(threadKey);
168
mConversationsCacheKeys.remove(threadKey);
172
// check if this is a main key first
173
if (mConversationsCache.contains(threadKey)) {
174
// Remove itself from the list and promote the next grouped thread if any
175
History::Threads threads = mConversationsCache[threadKey];
176
threads.removeAll(thread);
177
mConversationsCache.remove(threadKey);
178
mConversationsCacheKeys.remove(threadKey);
179
// remove all threads from reverse map. they will be readded
180
// in updateDisplayedThread() if needed
181
Q_FOREACH (const History::Thread &thread, threads) {
182
mConversationsCacheKeys.remove(generateThreadMapKey(thread));
184
if (!threads.isEmpty()) {
185
threadKey = generateThreadMapKey(threads.first());
186
mConversationsCache[threadKey] = threads;
187
updateDisplayedThread(threadKey);
190
// check if it belongs to an existing grouped thread;
191
QMap<QString, History::Threads>::iterator it = mConversationsCache.begin();
192
while (it != mConversationsCache.end()) {
193
const QString &threadKey = it.key();
194
History::Threads threads = it.value();
195
int pos = threads.indexOf(thread);
197
const QString &threadKey = generateThreadMapKey(thread);
198
mConversationsCache.remove(threadKey);
199
mConversationsCacheKeys.remove(threadKey);
200
if (threads.size() == 1) {
203
threads.removeAll(thread);
204
const QString &newThreadKey = generateThreadMapKey(threads.first());
205
mConversationsCache[newThreadKey] = threads;
206
updateDisplayedThread(newThreadKey);
44
216
History::PluginThreadView *SQLiteHistoryPlugin::queryThreads(History::EventType type,
45
217
const History::Sort &sort,
46
const History::Filter &filter)
218
const History::Filter &filter,
219
const QVariantMap &properties)
48
return new SQLiteHistoryThreadView(this, type, sort, filter);
221
return new SQLiteHistoryThreadView(this, type, sort, filter, properties);
51
224
History::PluginEventView *SQLiteHistoryPlugin::queryEvents(History::EventType type,
97
270
QString existingThread;
98
if (participants.count() == 1 && !threadIds.isEmpty()) {
99
existingThread = threadIds.first();
271
QStringList normalizedParticipants;
273
Q_FOREACH(const QString &participant, participants) {
274
normalizedParticipants << PhoneUtils::normalizePhoneNumber(participant);
101
QStringList normalizedParticipants;
103
Q_FOREACH(const QString &participant, participants) {
104
normalizedParticipants << PhoneUtils::normalizePhoneNumber(participant);
107
normalizedParticipants = participants;
110
// now for each threadId, check if all the other participants are listed
111
Q_FOREACH(const QString &threadId, threadIds) {
112
queryString = "SELECT %1 FROM thread_participants WHERE "
113
"threadId=:threadId AND type=:type AND accountId=:accountId";
114
query.prepare(queryString.arg(phoneCompare ? "normalizedId" : "participantId"));
115
query.bindValue(":threadId", threadId);
116
query.bindValue(":type", type);
117
query.bindValue(":accountId", accountId);
119
qCritical() << "Error:" << query.lastError() << query.lastQuery();
120
return QVariantMap();
123
QStringList threadParticipants;
124
while (query.next()) {
125
threadParticipants << query.value(0).toString();
128
// we can't use query.size() as it always return -1
129
if (threadParticipants.count() != participants.count()) {
133
// and now compare the lists
135
Q_FOREACH(const QString &participant, normalizedParticipants) {
137
// we need to iterate the list and call the phone number comparing function for
138
// each participant from the given thread
140
QStringList::iterator it = threadParticipants.begin();
141
while (it != threadParticipants.end()) {
142
if (PhoneUtils::compareNormalizedPhoneNumbers(*it, participant)) {
144
threadParticipants.erase(it);
153
} else if (!threadParticipants.contains(participant)) {
160
existingThread = threadId;
277
normalizedParticipants = participants;
280
// now for each threadId, check if all the other participants are listed
281
Q_FOREACH(const QString &threadId, threadIds) {
282
queryString = "SELECT %1 FROM thread_participants WHERE "
283
"threadId=:threadId AND type=:type AND accountId=:accountId";
284
query.prepare(queryString.arg(phoneCompare ? "normalizedId" : "participantId"));
285
query.bindValue(":threadId", threadId);
286
query.bindValue(":type", type);
287
query.bindValue(":accountId", accountId);
289
qCritical() << "Error:" << query.lastError() << query.lastQuery();
290
return QVariantMap();
293
QStringList threadParticipants;
294
while (query.next()) {
295
threadParticipants << query.value(0).toString();
298
// we can't use query.size() as it always return -1
299
if (threadParticipants.count() != normalizedParticipants.count()) {
303
bool found = History::Utils::compareNormalizedParticipants(threadParticipants, normalizedParticipants, matchFlags);
305
existingThread = threadId;
188
QVariantMap SQLiteHistoryPlugin::getSingleThread(History::EventType type, const QString &accountId, const QString &threadId)
332
QVariantMap SQLiteHistoryPlugin::getSingleThread(History::EventType type, const QString &accountId, const QString &threadId, const QVariantMap &properties)
190
334
QVariantMap result;
335
bool grouped = false;
336
if (accountId.isEmpty() || threadId.isEmpty()) {
339
if (properties.contains(History::FieldGroupingProperty)) {
340
grouped = properties[History::FieldGroupingProperty].toString() == History::FieldParticipants;
343
const QString &threadKey = generateThreadMapKey(accountId, threadId);
344
// we have to find which conversation this thread belongs to
345
if (mConversationsCacheKeys.contains(threadKey)) {
347
// get the displayed thread now
348
const History::Threads &groupedThreads = mConversationsCache[mConversationsCacheKeys[threadKey]];
349
QVariantList finalGroupedThreads;
350
Q_FOREACH(const History::Thread &displayedThread, groupedThreads) {
351
finalGroupedThreads << displayedThread.properties();
352
if (generateThreadMapKey(displayedThread) == threadKey) {
353
result = displayedThread.properties();
356
result[History::FieldGroupedThreads] = QVariant::fromValue(finalGroupedThreads);
192
362
QString condition = QString("accountId=\"%1\" AND threadId=\"%2\"").arg(accountId, threadId);
193
363
QString queryText = sqlQueryForThreads(type, condition, QString::null);
524
715
return queryText;
527
QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query)
718
QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties)
529
720
QList<QVariantMap> threads;
530
721
QSqlQuery attachmentsQuery(SQLiteDatabase::instance()->database());
531
722
QList<QVariantMap> attachments;
723
bool grouped = false;
724
if (properties.contains(History::FieldGroupingProperty)) {
725
grouped = properties[History::FieldGroupingProperty].toString() == History::FieldParticipants;
532
727
while (query.next()) {
533
728
QVariantMap thread;
729
QString accountId = query.value(0).toString();
730
QString threadId = query.value(1).toString();
731
if (threadId.trimmed().isEmpty()) {
534
734
thread[History::FieldType] = (int) type;
535
thread[History::FieldAccountId] = query.value(0);
536
thread[History::FieldThreadId] = query.value(1);
735
thread[History::FieldAccountId] = accountId;
736
thread[History::FieldThreadId] = threadId;
738
const QString &threadKey = generateThreadMapKey(accountId, threadId);
739
if (mInitialised && type == History::EventTypeText &&
740
!mConversationsCache.contains(threadKey)) {
743
QVariantList groupedThreads;
744
if (mConversationsCache.contains(threadKey)) {
745
Q_FOREACH (const History::Thread &thread, mConversationsCache[threadKey]) {
746
groupedThreads << thread.properties();
749
thread[History::FieldGroupedThreads] = QVariant::fromValue(groupedThreads);
538
752
thread[History::FieldEventId] = query.value(2);
539
753
thread[History::FieldCount] = query.value(3);