~ci-train-bot/history-service/history-service-ubuntu-zesty-2629

« back to all changes in this revision

Viewing changes to plugins/sqlite/sqlitehistoryplugin.cpp

Initial cache implementation for grouped conversations.
Approved by: PS Jenkins bot, Gustavo Pichorim Boiko

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
#include "sqlitehistoryplugin.h"
23
23
#include "phoneutils_p.h"
 
24
#include "utils_p.h"
24
25
#include "sqlitedatabase.h"
25
26
#include "sqlitehistoryeventview.h"
26
27
#include "sqlitehistorythreadview.h"
27
28
#include "intersectionfilter.h"
28
29
#include "unionfilter.h"
 
30
#include "thread.h"
29
31
#include "utils_p.h"
30
32
#include <QDateTime>
31
33
#include <QDebug>
33
35
#include <QSqlError>
34
36
#include <QDBusMetaType>
35
37
 
 
38
QString generateThreadMapKey(const History::Thread &thread)
 
39
{
 
40
    return thread.accountId() + thread.threadId();
 
41
}
 
42
 
 
43
QString generateThreadMapKey(const QString &accountId, const QString &threadId)
 
44
{
 
45
    return accountId + threadId;
 
46
}
 
47
 
36
48
SQLiteHistoryPlugin::SQLiteHistoryPlugin(QObject *parent) :
37
 
    QObject(parent)
 
49
    QObject(parent), mInitialised(false)
38
50
{
39
51
    // just trigger the database creation or update
40
52
    SQLiteDatabase::instance();
 
53
 
 
54
    updateGroupedThreadsCache();
 
55
 
 
56
    mInitialised = true;
 
57
}
 
58
 
 
59
void SQLiteHistoryPlugin::updateGroupedThreadsCache()
 
60
{
 
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) {
 
66
            threads += page;
 
67
        } else {
 
68
            break;
 
69
        }
 
70
    }
 
71
    addThreadsToCache(threads);
 
72
}
 
73
 
 
74
void SQLiteHistoryPlugin::addThreadsToCache(const QList<QVariantMap> &threads)
 
75
{
 
76
    Q_FOREACH (const QVariantMap &properties, threads) {
 
77
        History::Thread thread = History::Thread::fromProperties(properties);
 
78
        const QString &threadKey = generateThreadMapKey(thread);
 
79
 
 
80
        if (thread.type() != History::EventTypeText) {
 
81
             continue;
 
82
        } else if (!History::Utils::shouldGroupAccount(thread.accountId())) {
 
83
            // never group non phone accounts
 
84
            mConversationsCache[threadKey] = History::Threads() << thread;
 
85
            mConversationsCacheKeys[threadKey] = threadKey;
 
86
            continue;
 
87
        }
 
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));
 
94
            }
 
95
            groupedThreads.removeAll(thread);
 
96
            groupedThreads.append(thread);
 
97
            mConversationsCache[conversationKey] = groupedThreads;
 
98
            mConversationsCacheKeys.remove(threadKey);
 
99
            updateDisplayedThread(conversationKey);
 
100
            continue;
 
101
        }
 
102
        // if not found, we have to iterate the list and compare phone numbers
 
103
        bool found = false;
 
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);
 
110
                if (found) {
 
111
                    Q_FOREACH(const History::Thread &groupedThread, groupedThreads) {
 
112
                        mConversationsCacheKeys.remove(generateThreadMapKey(groupedThread));
 
113
                    }
 
114
                    mConversationsCache[conversationKey] += thread;
 
115
                    updateDisplayedThread(conversationKey);
 
116
                    break;
 
117
                }
 
118
            }
 
119
            if (found) {
 
120
                break;
 
121
            }
 
122
            it++;
 
123
        }
 
124
        if (!found) {
 
125
            mConversationsCache[threadKey] = History::Threads() << thread;
 
126
            mConversationsCacheKeys[threadKey] = threadKey;
 
127
        }
 
128
    }
 
129
}
 
130
 
 
131
bool SQLiteHistoryPlugin::lessThan(const QVariantMap &left, const QVariantMap &right) const
 
132
{
 
133
    QVariant leftValue = left[History::FieldLastEventTimestamp];
 
134
    QVariant rightValue = right[History::FieldLastEventTimestamp];
 
135
 
 
136
    return leftValue < rightValue;
 
137
}
 
138
 
 
139
void SQLiteHistoryPlugin::updateDisplayedThread(const QString &displayedThreadKey)
 
140
{
 
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();
 
148
        }
 
149
    }
 
150
 
 
151
    QString newDisplayedThreadKey = generateThreadMapKey(displayedThread);
 
152
    mConversationsCache.remove(displayedThreadKey);
 
153
    mConversationsCache[newDisplayedThreadKey] = threads;
 
154
 
 
155
    // update reverse threadId -> conversationId map
 
156
    Q_FOREACH(const History::Thread &groupedThread, threads) {
 
157
        mConversationsCacheKeys[generateThreadMapKey(groupedThread)] = newDisplayedThreadKey;
 
158
    }
 
159
}
 
160
 
 
161
void SQLiteHistoryPlugin::removeThreadFromCache(const QVariantMap &properties)
 
162
{
 
163
    History::Thread thread = History::Thread::fromProperties(properties);
 
164
    QString threadKey = generateThreadMapKey(thread);
 
165
 
 
166
    if (thread.type() != History::EventTypeText || !History::Utils::shouldGroupAccount(thread.accountId())) {
 
167
        mConversationsCache.remove(threadKey);
 
168
        mConversationsCacheKeys.remove(threadKey);
 
169
        return;
 
170
    }
 
171
 
 
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));
 
183
        }
 
184
        if (!threads.isEmpty()) {
 
185
            threadKey = generateThreadMapKey(threads.first());
 
186
            mConversationsCache[threadKey] = threads;
 
187
            updateDisplayedThread(threadKey);
 
188
        }
 
189
    } else {
 
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);
 
196
            if (pos != -1) {
 
197
                const QString &threadKey = generateThreadMapKey(thread);
 
198
                mConversationsCache.remove(threadKey);
 
199
                mConversationsCacheKeys.remove(threadKey);
 
200
                if (threads.size() == 1) {
 
201
                   return;
 
202
                } else {
 
203
                    threads.removeAll(thread);
 
204
                    const QString &newThreadKey = generateThreadMapKey(threads.first());
 
205
                    mConversationsCache[newThreadKey] = threads;
 
206
                    updateDisplayedThread(newThreadKey);
 
207
                    return;
 
208
                }
 
209
            }
 
210
            it++;
 
211
        }
 
212
    }
41
213
}
42
214
 
43
215
// Reader
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)
47
220
{
48
 
    return new SQLiteHistoryThreadView(this, type, sort, filter);
 
221
    return new SQLiteHistoryThreadView(this, type, sort, filter, properties);
49
222
}
50
223
 
51
224
History::PluginEventView *SQLiteHistoryPlugin::queryEvents(History::EventType type,
95
268
    }
96
269
 
97
270
    QString existingThread;
98
 
    if (participants.count() == 1 && !threadIds.isEmpty()) {
99
 
         existingThread = threadIds.first();
 
271
    QStringList normalizedParticipants;
 
272
    if (phoneCompare) {
 
273
        Q_FOREACH(const QString &participant, participants) {
 
274
            normalizedParticipants << PhoneUtils::normalizePhoneNumber(participant);
 
275
        }
100
276
    } else {
101
 
        QStringList normalizedParticipants;
102
 
        if (phoneCompare) {
103
 
            Q_FOREACH(const QString &participant, participants) {
104
 
                normalizedParticipants << PhoneUtils::normalizePhoneNumber(participant);
105
 
            }
106
 
        } else {
107
 
            normalizedParticipants = participants;
108
 
        }
109
 
 
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);
118
 
            if (!query.exec()) {
119
 
                qCritical() << "Error:" << query.lastError() << query.lastQuery();
120
 
                return QVariantMap();
121
 
            }
122
 
 
123
 
            QStringList threadParticipants;
124
 
            while (query.next()) {
125
 
                threadParticipants << query.value(0).toString();
126
 
            }
127
 
 
128
 
            // we can't use query.size() as it always return -1
129
 
            if (threadParticipants.count() != participants.count()) {
130
 
                continue;
131
 
            }
132
 
 
133
 
            // and now compare the lists
134
 
            bool found = true;
135
 
            Q_FOREACH(const QString &participant, normalizedParticipants) {
136
 
                if (phoneCompare) {
137
 
                    // we need to iterate the list and call the phone number comparing function for
138
 
                    // each participant from the given thread
139
 
                    bool inList = false;
140
 
                    QStringList::iterator it = threadParticipants.begin();
141
 
                    while (it != threadParticipants.end()) {
142
 
                        if (PhoneUtils::compareNormalizedPhoneNumbers(*it, participant)) {
143
 
                            inList = true;
144
 
                            threadParticipants.erase(it);
145
 
                            break;
146
 
                        }
147
 
                        ++it;
148
 
                    }
149
 
                    if (!inList) {
150
 
                        found = false;
151
 
                        break;
152
 
                    }
153
 
                } else if (!threadParticipants.contains(participant)) {
154
 
                    found = false;
155
 
                    break;
156
 
                }
157
 
            }
158
 
 
159
 
            if (found) {
160
 
                existingThread = threadId;
161
 
                break;
162
 
            }
 
277
        normalizedParticipants = participants;
 
278
    }
 
279
 
 
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);
 
288
        if (!query.exec()) {
 
289
            qCritical() << "Error:" << query.lastError() << query.lastQuery();
 
290
            return QVariantMap();
 
291
        }
 
292
 
 
293
        QStringList threadParticipants;
 
294
        while (query.next()) {
 
295
            threadParticipants << query.value(0).toString();
 
296
        }
 
297
 
 
298
        // we can't use query.size() as it always return -1
 
299
        if (threadParticipants.count() != normalizedParticipants.count()) {
 
300
            continue;
 
301
        }
 
302
 
 
303
        bool found = History::Utils::compareNormalizedParticipants(threadParticipants, normalizedParticipants, matchFlags);
 
304
        if (found) {
 
305
            existingThread = threadId;
 
306
            break;
163
307
        }
164
308
    }
165
309
 
185
329
    return results;
186
330
}
187
331
 
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)
189
333
{
190
334
    QVariantMap result;
 
335
    bool grouped = false;
 
336
    if (accountId.isEmpty() || threadId.isEmpty()) {
 
337
        return result;
 
338
    }
 
339
    if (properties.contains(History::FieldGroupingProperty)) {
 
340
        grouped = properties[History::FieldGroupingProperty].toString() == History::FieldParticipants;
 
341
    }
 
342
    if (grouped) {
 
343
        const QString &threadKey = generateThreadMapKey(accountId, threadId);
 
344
        // we have to find which conversation this thread belongs to
 
345
        if (mConversationsCacheKeys.contains(threadKey)) {
 
346
            // found the thread.
 
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();
 
354
                }
 
355
            }
 
356
            result[History::FieldGroupedThreads] = QVariant::fromValue(finalGroupedThreads);
 
357
            return result;
 
358
        }
 
359
        return result;
 
360
    }
191
361
 
192
362
    QString condition = QString("accountId=\"%1\" AND threadId=\"%2\"").arg(accountId, threadId);
193
363
    QString queryText = sqlQueryForThreads(type, condition, QString::null);
199
369
        return result;
200
370
    }
201
371
 
202
 
    QList<QVariantMap> results = parseThreadResults(type, query);
 
372
    QList<QVariantMap> results = parseThreadResults(type, query, properties);
203
373
    query.clear();
204
374
    if (!results.isEmpty()) {
205
375
        result = results.first();
278
448
    thread[History::FieldCount] = 0;
279
449
    thread[History::FieldUnreadCount] = 0;
280
450
 
 
451
    addThreadsToCache(QList<QVariantMap>() << thread);
 
452
 
281
453
    return thread;
282
454
}
283
455
 
295
467
        return false;
296
468
    }
297
469
 
 
470
    removeThreadFromCache(thread);
 
471
 
298
472
    return true;
299
473
}
300
474
 
371
545
        }
372
546
    }
373
547
 
 
548
    if (result == History::EventWriteModified || result == History::EventWriteCreated) {
 
549
        QVariantMap existingThread = getSingleThread((History::EventType) event[History::FieldType].toInt(),
 
550
                                                     event[History::FieldAccountId].toString(),
 
551
                                                     event[History::FieldThreadId].toString(),
 
552
                                                     QVariantMap());
 
553
        addThreadsToCache(QList<QVariantMap>() << existingThread);
 
554
 
 
555
    }
 
556
 
374
557
    return result;
375
558
}
376
559
 
384
567
    query.bindValue(":eventId", event[History::FieldEventId]);
385
568
 
386
569
    if (!query.exec()) {
387
 
        qCritical() << "Failed to save the voice event: Error:" << query.lastError() << query.lastQuery();
 
570
        qCritical() << "Failed to remove the text event: Error:" << query.lastError() << query.lastQuery();
388
571
        return false;
389
572
    }
390
573
 
 
574
    QVariantMap existingThread = getSingleThread((History::EventType) event[History::FieldType].toInt(),
 
575
                                                 event[History::FieldAccountId].toString(),
 
576
                                                 event[History::FieldThreadId].toString(),
 
577
                                                 QVariantMap());
 
578
    if (!existingThread.isEmpty()) {
 
579
        addThreadsToCache(QList<QVariantMap>() << existingThread);
 
580
    }
 
581
 
391
582
    return true;
392
583
}
393
584
 
524
715
    return queryText;
525
716
}
526
717
 
527
 
QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query)
 
718
QList<QVariantMap> SQLiteHistoryPlugin::parseThreadResults(History::EventType type, QSqlQuery &query, const QVariantMap &properties)
528
719
{
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;
 
726
    }
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()) {
 
732
            continue;
 
733
        }
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;
 
737
        if (grouped) {
 
738
            const QString &threadKey = generateThreadMapKey(accountId, threadId);
 
739
            if (mInitialised && type == History::EventTypeText && 
 
740
                !mConversationsCache.contains(threadKey)) {
 
741
                continue;
 
742
            }
 
743
            QVariantList groupedThreads;
 
744
            if (mConversationsCache.contains(threadKey)) {
 
745
                Q_FOREACH (const History::Thread &thread, mConversationsCache[threadKey]) {
 
746
                    groupedThreads << thread.properties();
 
747
                }
 
748
            }
 
749
            thread[History::FieldGroupedThreads] = QVariant::fromValue(groupedThreads);
 
750
        }
537
751
 
538
752
        thread[History::FieldEventId] = query.value(2);
539
753
        thread[History::FieldCount] = query.value(3);