diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2016-10-13 18:38:35 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-02-11 23:03:17 +0100 |
commit | 6051c1247cde61bcc8e483eb4166e5a297c0ecc6 (patch) | |
tree | df3aba1ef4011f2640b17c8cf7a9b106933231ab | |
parent | 8740a007515dcf1b315d69ab5c64fcfd40ec980c (diff) | |
download | sink-6051c1247cde61bcc8e483eb4166e5a297c0ecc6.tar.gz sink-6051c1247cde61bcc8e483eb4166e5a297c0ecc6.zip |
Xapian based fulltext indexing
This cuts into the sync performance by about 40%,
but gives us fast fulltext searching for all local content.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | common/CMakeLists.txt | 3 | ||||
-rw-r--r-- | common/datastorequery.cpp | 4 | ||||
-rw-r--r-- | common/definitions.cpp | 2 | ||||
-rw-r--r-- | common/domain/typeimplementations.cpp | 4 | ||||
-rw-r--r-- | common/fulltextindex.cpp | 149 | ||||
-rw-r--r-- | common/fulltextindex.h | 39 | ||||
-rw-r--r-- | common/indexer.cpp | 3 | ||||
-rw-r--r-- | common/indexer.h | 6 | ||||
-rw-r--r-- | common/mail/fulltextindexer.cpp | 64 | ||||
-rw-r--r-- | common/mail/fulltextindexer.h | 39 | ||||
-rw-r--r-- | common/mail/threadindexer.cpp | 5 | ||||
-rw-r--r-- | common/mail/threadindexer.h | 1 | ||||
-rw-r--r-- | common/mailpreprocessor.cpp | 40 | ||||
-rw-r--r-- | common/mailpreprocessor.h | 1 | ||||
-rw-r--r-- | common/query.cpp | 3 | ||||
-rw-r--r-- | common/query.h | 3 | ||||
-rw-r--r-- | common/storage/entitystore.cpp | 22 | ||||
-rw-r--r-- | common/typeindex.cpp | 46 | ||||
-rw-r--r-- | common/typeindex.h | 11 | ||||
-rw-r--r-- | synchronizer/CMakeLists.txt | 1 | ||||
-rw-r--r-- | synchronizer/main.cpp | 4 | ||||
-rw-r--r-- | tests/dummyresourcewritebenchmark.cpp | 42 | ||||
-rw-r--r-- | tests/querytest.cpp | 90 |
24 files changed, 519 insertions, 64 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index bb6df73..3327afd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -36,6 +36,7 @@ find_package(KF5 COMPONENTS REQUIRED Mime Contacts) | |||
36 | find_package(FlatBuffers REQUIRED 1.4.0) | 36 | find_package(FlatBuffers REQUIRED 1.4.0) |
37 | find_package(KAsync REQUIRED 0.1.2) | 37 | find_package(KAsync REQUIRED 0.1.2) |
38 | find_package(LMDB REQUIRED 0.9) | 38 | find_package(LMDB REQUIRED 0.9) |
39 | find_package(Xapian REQUIRED 1.4) | ||
39 | 40 | ||
40 | if (${ENABLE_MEMCHECK}) | 41 | if (${ENABLE_MEMCHECK}) |
41 | message("Enabled memcheck") | 42 | message("Enabled memcheck") |
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index ec83f6f..76579dd 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt | |||
@@ -75,11 +75,13 @@ set(command_SRCS | |||
75 | storage/entitystore.cpp | 75 | storage/entitystore.cpp |
76 | indexer.cpp | 76 | indexer.cpp |
77 | mail/threadindexer.cpp | 77 | mail/threadindexer.cpp |
78 | mail/fulltextindexer.cpp | ||
78 | notification.cpp | 79 | notification.cpp |
79 | commandprocessor.cpp | 80 | commandprocessor.cpp |
80 | inspector.cpp | 81 | inspector.cpp |
81 | propertyparser.cpp | 82 | propertyparser.cpp |
82 | utils.cpp | 83 | utils.cpp |
84 | fulltextindex.cpp | ||
83 | ${storage_SRCS}) | 85 | ${storage_SRCS}) |
84 | 86 | ||
85 | add_library(${PROJECT_NAME} SHARED ${command_SRCS}) | 87 | add_library(${PROJECT_NAME} SHARED ${command_SRCS}) |
@@ -127,6 +129,7 @@ PRIVATE | |||
127 | Qt5::Gui | 129 | Qt5::Gui |
128 | KF5::Mime | 130 | KF5::Mime |
129 | KF5::Contacts | 131 | KF5::Contacts |
132 | ${XAPIAN_LIBRARIES} | ||
130 | ) | 133 | ) |
131 | install(TARGETS ${PROJECT_NAME} | 134 | install(TARGETS ${PROJECT_NAME} |
132 | EXPORT SinkTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) | 135 | EXPORT SinkTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) |
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp index 870daf8..3218d1a 100644 --- a/common/datastorequery.cpp +++ b/common/datastorequery.cpp | |||
@@ -160,6 +160,10 @@ public: | |||
160 | for (const auto &filterProperty : propertyFilter.keys()) { | 160 | for (const auto &filterProperty : propertyFilter.keys()) { |
161 | const auto property = entity.getProperty(filterProperty); | 161 | const auto property = entity.getProperty(filterProperty); |
162 | const auto comparator = propertyFilter.value(filterProperty); | 162 | const auto comparator = propertyFilter.value(filterProperty); |
163 | //We can't deal with a fulltext filter | ||
164 | if (comparator.comparator == QueryBase::Comparator::Fulltext) { | ||
165 | continue; | ||
166 | } | ||
163 | if (!comparator.matches(property)) { | 167 | if (!comparator.matches(property)) { |
164 | SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << entity.identifier() << "Property: " << filterProperty << property << " Filter:" << comparator.value; | 168 | SinkTraceCtx(mDatastore->mLogCtx) << "Filtering entity due to property mismatch on filter: " << entity.identifier() << "Property: " << filterProperty << property << " Filter:" << comparator.value; |
165 | return false; | 169 | return false; |
diff --git a/common/definitions.cpp b/common/definitions.cpp index b22137a..642b68c 100644 --- a/common/definitions.cpp +++ b/common/definitions.cpp | |||
@@ -90,5 +90,5 @@ QString Sink::resourceStorageLocation(const QByteArray &resourceInstanceIdentifi | |||
90 | 90 | ||
91 | qint64 Sink::latestDatabaseVersion() | 91 | qint64 Sink::latestDatabaseVersion() |
92 | { | 92 | { |
93 | return 1; | 93 | return 2; |
94 | } | 94 | } |
diff --git a/common/domain/typeimplementations.cpp b/common/domain/typeimplementations.cpp index 47a9cf7..29da7ea 100644 --- a/common/domain/typeimplementations.cpp +++ b/common/domain/typeimplementations.cpp | |||
@@ -27,6 +27,7 @@ | |||
27 | #include "entitybuffer.h" | 27 | #include "entitybuffer.h" |
28 | #include "entity_generated.h" | 28 | #include "entity_generated.h" |
29 | #include "mail/threadindexer.h" | 29 | #include "mail/threadindexer.h" |
30 | #include "mail/fulltextindexer.h" | ||
30 | #include "domainadaptor.h" | 31 | #include "domainadaptor.h" |
31 | #include "typeimplementations_p.h" | 32 | #include "typeimplementations_p.h" |
32 | 33 | ||
@@ -45,7 +46,8 @@ typedef IndexConfig<Mail, | |||
45 | SortedIndex<Mail::Folder, Mail::Date>, | 46 | SortedIndex<Mail::Folder, Mail::Date>, |
46 | SecondaryIndex<Mail::MessageId, Mail::ThreadId>, | 47 | SecondaryIndex<Mail::MessageId, Mail::ThreadId>, |
47 | SecondaryIndex<Mail::ThreadId, Mail::MessageId>, | 48 | SecondaryIndex<Mail::ThreadId, Mail::MessageId>, |
48 | CustomSecondaryIndex<Mail::MessageId, Mail::ThreadId, ThreadIndexer> | 49 | CustomSecondaryIndex<Mail::MessageId, Mail::ThreadId, ThreadIndexer>, |
50 | CustomSecondaryIndex<Mail::Subject, Mail::Subject, FulltextIndexer> | ||
49 | > MailIndexConfig; | 51 | > MailIndexConfig; |
50 | 52 | ||
51 | typedef IndexConfig<Folder, | 53 | typedef IndexConfig<Folder, |
diff --git a/common/fulltextindex.cpp b/common/fulltextindex.cpp new file mode 100644 index 0000000..33972b7 --- /dev/null +++ b/common/fulltextindex.cpp | |||
@@ -0,0 +1,149 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2018 Christian Mollekopf <mollekopf@kolabsys.com> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | //xapian.h needs to be included first to build | ||
20 | #include <xapian.h> | ||
21 | #include "fulltextindex.h" | ||
22 | |||
23 | #include <QFile> | ||
24 | #include <QDir> | ||
25 | |||
26 | #include "log.h" | ||
27 | #include "definitions.h" | ||
28 | |||
29 | FulltextIndex::FulltextIndex(const QByteArray &resourceInstanceIdentifier, Sink::Storage::DataStore::AccessMode accessMode) | ||
30 | : mName("fulltext"), | ||
31 | mDbPath{QFile::encodeName(Sink::resourceStorageLocation(resourceInstanceIdentifier) + '/' + "fulltext")} | ||
32 | { | ||
33 | try { | ||
34 | if (QDir{}.mkpath(mDbPath)) { | ||
35 | if (accessMode == Sink::Storage::DataStore::ReadWrite) { | ||
36 | mDb = new Xapian::WritableDatabase(mDbPath.toStdString(), Xapian::DB_CREATE_OR_OPEN); | ||
37 | } else { | ||
38 | mDb = new Xapian::Database(mDbPath.toStdString(), Xapian::DB_OPEN); | ||
39 | } | ||
40 | } else { | ||
41 | SinkError() << "Failed to open database" << mDbPath; | ||
42 | } | ||
43 | } catch (const Xapian::DatabaseError& e) { | ||
44 | SinkError() << "Failed to open database" << mDbPath << ":" << QString::fromStdString(e.get_msg()); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | FulltextIndex::~FulltextIndex() | ||
49 | { | ||
50 | delete mDb; | ||
51 | } | ||
52 | |||
53 | static std::string idTerm(const QByteArray &key) | ||
54 | { | ||
55 | return "Q" + key.toStdString(); | ||
56 | } | ||
57 | |||
58 | void FulltextIndex::add(const QByteArray &key, const QString &value) | ||
59 | { | ||
60 | add(key, {{{}, value}}); | ||
61 | } | ||
62 | |||
63 | void FulltextIndex::add(const QByteArray &key, const QList<QPair<QString, QString>> &values) | ||
64 | { | ||
65 | if (!mDb) { | ||
66 | return; | ||
67 | } | ||
68 | Xapian::TermGenerator generator; | ||
69 | Xapian::Document document; | ||
70 | generator.set_document(document); | ||
71 | |||
72 | for (const auto &entry : values) { | ||
73 | if (!entry.second.isEmpty()) { | ||
74 | generator.index_text(entry.second.toStdString()); | ||
75 | } | ||
76 | } | ||
77 | document.add_value(0, key.toStdString()); | ||
78 | |||
79 | const auto idterm = idTerm(key); | ||
80 | document.add_boolean_term(idterm); | ||
81 | |||
82 | writableDatabase()->replace_document(idterm, document); | ||
83 | } | ||
84 | |||
85 | void FulltextIndex::commitTransaction() | ||
86 | { | ||
87 | if (mHasTransactionOpen) { | ||
88 | Q_ASSERT(mDb); | ||
89 | writableDatabase()->commit_transaction(); | ||
90 | mHasTransactionOpen = false; | ||
91 | } | ||
92 | } | ||
93 | |||
94 | void FulltextIndex::abortTransaction() | ||
95 | { | ||
96 | if (mHasTransactionOpen) { | ||
97 | Q_ASSERT(mDb); | ||
98 | writableDatabase()->cancel_transaction(); | ||
99 | mHasTransactionOpen = false; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | Xapian::WritableDatabase* FulltextIndex::writableDatabase() | ||
104 | { | ||
105 | Q_ASSERT(dynamic_cast<Xapian::WritableDatabase*>(mDb)); | ||
106 | auto db = static_cast<Xapian::WritableDatabase*>(mDb); | ||
107 | if (!mHasTransactionOpen) { | ||
108 | db->begin_transaction(); | ||
109 | mHasTransactionOpen = true; | ||
110 | } | ||
111 | return db; | ||
112 | } | ||
113 | |||
114 | void FulltextIndex::remove(const QByteArray &key) | ||
115 | { | ||
116 | if (!mDb) { | ||
117 | return; | ||
118 | } | ||
119 | writableDatabase()->delete_document(idTerm(key)); | ||
120 | } | ||
121 | |||
122 | QVector<QByteArray> FulltextIndex::lookup(const QString &searchTerm) | ||
123 | { | ||
124 | if (!mDb) { | ||
125 | return {}; | ||
126 | } | ||
127 | QVector<QByteArray> results; | ||
128 | |||
129 | try { | ||
130 | Xapian::QueryParser parser; | ||
131 | auto query = parser.parse_query(searchTerm.toStdString(), Xapian::QueryParser::FLAG_WILDCARD|Xapian::QueryParser::FLAG_PHRASE|Xapian::QueryParser::FLAG_BOOLEAN|Xapian::QueryParser::FLAG_LOVEHATE); | ||
132 | Xapian::Enquire enquire(*mDb); | ||
133 | enquire.set_query(query); | ||
134 | |||
135 | auto limit = 1000; | ||
136 | Xapian::MSet mset = enquire.get_mset(0, limit); | ||
137 | Xapian::MSetIterator it = mset.begin(); | ||
138 | for (;it != mset.end(); it++) { | ||
139 | auto doc = it.get_document(); | ||
140 | const auto data = doc.get_value(0); | ||
141 | results << QByteArray{data.c_str(), int(data.length())}; | ||
142 | } | ||
143 | } | ||
144 | catch (const Xapian::Error &error) { | ||
145 | // Nothing to do, move along | ||
146 | } | ||
147 | return results; | ||
148 | } | ||
149 | |||
diff --git a/common/fulltextindex.h b/common/fulltextindex.h new file mode 100644 index 0000000..e06f29d --- /dev/null +++ b/common/fulltextindex.h | |||
@@ -0,0 +1,39 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "sink_export.h" | ||
4 | |||
5 | #include <string> | ||
6 | #include <functional> | ||
7 | #include <QString> | ||
8 | #include <memory> | ||
9 | #include "storage.h" | ||
10 | #include "log.h" | ||
11 | |||
12 | namespace Xapian { | ||
13 | class Database; | ||
14 | class WritableDatabase; | ||
15 | }; | ||
16 | |||
17 | class SINK_EXPORT FulltextIndex | ||
18 | { | ||
19 | public: | ||
20 | FulltextIndex(const QByteArray &resourceInstanceIdentifier, Sink::Storage::DataStore::AccessMode mode = Sink::Storage::DataStore::ReadOnly); | ||
21 | ~FulltextIndex(); | ||
22 | |||
23 | void add(const QByteArray &key, const QString &value); | ||
24 | void add(const QByteArray &key, const QList<QPair<QString, QString>> &values); | ||
25 | void remove(const QByteArray &key); | ||
26 | |||
27 | void commitTransaction(); | ||
28 | void abortTransaction(); | ||
29 | |||
30 | QVector<QByteArray> lookup(const QString &key); | ||
31 | |||
32 | private: | ||
33 | Xapian::WritableDatabase* writableDatabase(); | ||
34 | Q_DISABLE_COPY(FulltextIndex); | ||
35 | Xapian::Database *mDb{nullptr}; | ||
36 | QString mName; | ||
37 | QString mDbPath; | ||
38 | bool mHasTransactionOpen{false}; | ||
39 | }; | ||
diff --git a/common/indexer.cpp b/common/indexer.cpp index 1b223b3..c18170c 100644 --- a/common/indexer.cpp +++ b/common/indexer.cpp | |||
@@ -20,10 +20,11 @@ | |||
20 | 20 | ||
21 | using namespace Sink; | 21 | using namespace Sink; |
22 | 22 | ||
23 | void Indexer::setup(TypeIndex *index, Storage::DataStore::Transaction *transaction) | 23 | void Indexer::setup(TypeIndex *index, Storage::DataStore::Transaction *transaction, const QByteArray &resourceId) |
24 | { | 24 | { |
25 | mTypeIndex = index; | 25 | mTypeIndex = index; |
26 | mTransaction = transaction; | 26 | mTransaction = transaction; |
27 | mResourceInstanceIdentifier = resourceId; | ||
27 | } | 28 | } |
28 | 29 | ||
29 | Storage::DataStore::Transaction &Indexer::transaction() | 30 | Storage::DataStore::Transaction &Indexer::transaction() |
diff --git a/common/indexer.h b/common/indexer.h index 26887fb..f0b32f5 100644 --- a/common/indexer.h +++ b/common/indexer.h | |||
@@ -33,16 +33,18 @@ public: | |||
33 | virtual ~Indexer() = default; | 33 | virtual ~Indexer() = default; |
34 | typedef QSharedPointer<Indexer> Ptr; | 34 | typedef QSharedPointer<Indexer> Ptr; |
35 | virtual void add(const ApplicationDomain::ApplicationDomainType &entity) = 0; | 35 | virtual void add(const ApplicationDomain::ApplicationDomainType &entity) = 0; |
36 | virtual void modify(const ApplicationDomain::ApplicationDomainType &old, const ApplicationDomain::ApplicationDomainType &entity) = 0; | ||
37 | virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) = 0; | 36 | virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) = 0; |
37 | virtual void commitTransaction() {}; | ||
38 | virtual void abortTransaction() {}; | ||
38 | 39 | ||
39 | protected: | 40 | protected: |
40 | Storage::DataStore::Transaction &transaction(); | 41 | Storage::DataStore::Transaction &transaction(); |
41 | TypeIndex &index(); | 42 | TypeIndex &index(); |
43 | QByteArray mResourceInstanceIdentifier; | ||
42 | 44 | ||
43 | private: | 45 | private: |
44 | friend class ::TypeIndex; | 46 | friend class ::TypeIndex; |
45 | void setup(TypeIndex *, Storage::DataStore::Transaction *); | 47 | void setup(TypeIndex *, Storage::DataStore::Transaction *, const QByteArray &resourceId); |
46 | Storage::DataStore::Transaction *mTransaction; | 48 | Storage::DataStore::Transaction *mTransaction; |
47 | TypeIndex *mTypeIndex; | 49 | TypeIndex *mTypeIndex; |
48 | }; | 50 | }; |
diff --git a/common/mail/fulltextindexer.cpp b/common/mail/fulltextindexer.cpp new file mode 100644 index 0000000..a980752 --- /dev/null +++ b/common/mail/fulltextindexer.cpp | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | #include "fulltextindexer.h" | ||
20 | |||
21 | #include "typeindex.h" | ||
22 | #include "fulltextindex.h" | ||
23 | #include "log.h" | ||
24 | #include "utils.h" | ||
25 | |||
26 | using namespace Sink; | ||
27 | using namespace Sink::ApplicationDomain; | ||
28 | |||
29 | |||
30 | void FulltextIndexer::add(const ApplicationDomain::ApplicationDomainType &entity) | ||
31 | { | ||
32 | if (!index) { | ||
33 | index.reset(new FulltextIndex{mResourceInstanceIdentifier, Storage::DataStore::ReadWrite}); | ||
34 | } | ||
35 | index->add(entity.identifier(), entity.getProperty("index").value<QList<QPair<QString, QString>>>()); | ||
36 | } | ||
37 | |||
38 | void FulltextIndexer::remove(const ApplicationDomain::ApplicationDomainType &entity) | ||
39 | { | ||
40 | if (!index) { | ||
41 | index.reset(new FulltextIndex{mResourceInstanceIdentifier, Storage::DataStore::ReadWrite}); | ||
42 | } | ||
43 | index->remove(entity.identifier()); | ||
44 | } | ||
45 | |||
46 | void FulltextIndexer::commitTransaction() | ||
47 | { | ||
48 | if (index) { | ||
49 | index->commitTransaction(); | ||
50 | } | ||
51 | } | ||
52 | |||
53 | void FulltextIndexer::abortTransaction() | ||
54 | { | ||
55 | if (index) { | ||
56 | index->abortTransaction(); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | QMap<QByteArray, int> FulltextIndexer::databases() | ||
61 | { | ||
62 | return {}; | ||
63 | } | ||
64 | |||
diff --git a/common/mail/fulltextindexer.h b/common/mail/fulltextindexer.h new file mode 100644 index 0000000..ef41455 --- /dev/null +++ b/common/mail/fulltextindexer.h | |||
@@ -0,0 +1,39 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | #pragma once | ||
20 | |||
21 | #include "indexer.h" | ||
22 | |||
23 | class FulltextIndex; | ||
24 | namespace Sink { | ||
25 | |||
26 | class FulltextIndexer : public Indexer | ||
27 | { | ||
28 | public: | ||
29 | typedef QSharedPointer<FulltextIndexer> Ptr; | ||
30 | virtual void add(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; | ||
31 | virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; | ||
32 | virtual void commitTransaction() Q_DECL_OVERRIDE; | ||
33 | virtual void abortTransaction() Q_DECL_OVERRIDE; | ||
34 | static QMap<QByteArray, int> databases(); | ||
35 | private: | ||
36 | QSharedPointer<FulltextIndex> index; | ||
37 | }; | ||
38 | |||
39 | } | ||
diff --git a/common/mail/threadindexer.cpp b/common/mail/threadindexer.cpp index 2e6a6e7..fb47118 100644 --- a/common/mail/threadindexer.cpp +++ b/common/mail/threadindexer.cpp | |||
@@ -98,11 +98,6 @@ void ThreadIndexer::add(const ApplicationDomain::ApplicationDomainType &entity) | |||
98 | updateThreadingIndex(entity.identifier(), entity, transaction()); | 98 | updateThreadingIndex(entity.identifier(), entity, transaction()); |
99 | } | 99 | } |
100 | 100 | ||
101 | void ThreadIndexer::modify(const ApplicationDomain::ApplicationDomainType &old, const ApplicationDomain::ApplicationDomainType &entity) | ||
102 | { | ||
103 | |||
104 | } | ||
105 | |||
106 | void ThreadIndexer::remove(const ApplicationDomain::ApplicationDomainType &entity) | 101 | void ThreadIndexer::remove(const ApplicationDomain::ApplicationDomainType &entity) |
107 | { | 102 | { |
108 | auto messageId = entity.getProperty(Mail::MessageId::name); | 103 | auto messageId = entity.getProperty(Mail::MessageId::name); |
diff --git a/common/mail/threadindexer.h b/common/mail/threadindexer.h index 60d0863..b2e939a 100644 --- a/common/mail/threadindexer.h +++ b/common/mail/threadindexer.h | |||
@@ -27,7 +27,6 @@ class ThreadIndexer : public Indexer | |||
27 | public: | 27 | public: |
28 | typedef QSharedPointer<ThreadIndexer> Ptr; | 28 | typedef QSharedPointer<ThreadIndexer> Ptr; |
29 | virtual void add(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; | 29 | virtual void add(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; |
30 | virtual void modify(const ApplicationDomain::ApplicationDomainType &old, const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; | ||
31 | virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; | 30 | virtual void remove(const ApplicationDomain::ApplicationDomainType &entity) Q_DECL_OVERRIDE; |
32 | static QMap<QByteArray, int> databases(); | 31 | static QMap<QByteArray, int> databases(); |
33 | private: | 32 | private: |
diff --git a/common/mailpreprocessor.cpp b/common/mailpreprocessor.cpp index 8f5a77d..58cb15b 100644 --- a/common/mailpreprocessor.cpp +++ b/common/mailpreprocessor.cpp | |||
@@ -21,9 +21,11 @@ | |||
21 | 21 | ||
22 | #include <QFile> | 22 | #include <QFile> |
23 | #include <QDir> | 23 | #include <QDir> |
24 | #include <QTextDocument> | ||
24 | #include <KMime/KMime/KMimeMessage> | 25 | #include <KMime/KMime/KMimeMessage> |
25 | 26 | ||
26 | #include "pipeline.h" | 27 | #include "pipeline.h" |
28 | #include "fulltextindex.h" | ||
27 | #include "definitions.h" | 29 | #include "definitions.h" |
28 | #include "applicationdomaintype.h" | 30 | #include "applicationdomaintype.h" |
29 | 31 | ||
@@ -45,13 +47,34 @@ static QList<Sink::ApplicationDomain::Mail::Contact> getContactList(const KMime: | |||
45 | return list; | 47 | return list; |
46 | } | 48 | } |
47 | 49 | ||
50 | static QList<QPair<QString, QString>> processPart(KMime::Content* content) | ||
51 | { | ||
52 | if (KMime::Headers::ContentType* type = content->contentType(false)) { | ||
53 | if (type->isMultipart() && !type->isSubtype("encrypted")) { | ||
54 | QList<QPair<QString, QString>> list; | ||
55 | for (const auto c : content->contents()) { | ||
56 | list << processPart(c); | ||
57 | } | ||
58 | return list; | ||
59 | } else if (type->isHTMLText()) { | ||
60 | // Only get HTML content, if no plain text content | ||
61 | QTextDocument doc; | ||
62 | doc.setHtml(content->decodedText()); | ||
63 | return {{{}, {doc.toPlainText()}}}; | ||
64 | } else if (type->isEmpty()) { | ||
65 | return {{{}, {content->decodedText()}}}; | ||
66 | } | ||
67 | } | ||
68 | return {}; | ||
69 | } | ||
70 | |||
48 | void MailPropertyExtractor::updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, const QByteArray &data) | 71 | void MailPropertyExtractor::updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, const QByteArray &data) |
49 | { | 72 | { |
50 | if (data.isEmpty()) { | 73 | if (data.isEmpty()) { |
51 | return; | 74 | return; |
52 | } | 75 | } |
53 | auto msg = KMime::Message::Ptr(new KMime::Message); | 76 | auto msg = KMime::Message::Ptr(new KMime::Message); |
54 | msg->setHead(KMime::CRLFtoLF(data)); | 77 | msg->setContent(KMime::CRLFtoLF(data)); |
55 | msg->parse(); | 78 | msg->parse(); |
56 | if (!msg) { | 79 | if (!msg) { |
57 | return; | 80 | return; |
@@ -103,6 +126,20 @@ void MailPropertyExtractor::updatedIndexedProperties(Sink::ApplicationDomain::Ma | |||
103 | if (!parentMessageId.isEmpty()) { | 126 | if (!parentMessageId.isEmpty()) { |
104 | mail.setExtractedParentMessageId(parentMessageId); | 127 | mail.setExtractedParentMessageId(parentMessageId); |
105 | } | 128 | } |
129 | QList<QPair<QString, QString>> contentToIndex; | ||
130 | contentToIndex.append({{}, msg->subject()->asUnicodeString()}); | ||
131 | if (KMime::Content* mainBody = msg->mainBodyPart("text/plain")) { | ||
132 | contentToIndex.append({{}, mainBody->decodedText()}); | ||
133 | } else { | ||
134 | contentToIndex << processPart(msg.data()); | ||
135 | } | ||
136 | contentToIndex.append({{}, msg->from(true)->asUnicodeString()}); | ||
137 | contentToIndex.append({{}, msg->to(true)->asUnicodeString()}); | ||
138 | contentToIndex.append({{}, msg->cc(true)->asUnicodeString()}); | ||
139 | contentToIndex.append({{}, msg->bcc(true)->asUnicodeString()}); | ||
140 | |||
141 | //Prepare content for indexing; | ||
142 | mail.setProperty("index", QVariant::fromValue(contentToIndex)); | ||
106 | } | 143 | } |
107 | 144 | ||
108 | void MailPropertyExtractor::newEntity(Sink::ApplicationDomain::Mail &mail) | 145 | void MailPropertyExtractor::newEntity(Sink::ApplicationDomain::Mail &mail) |
@@ -114,4 +151,3 @@ void MailPropertyExtractor::modifiedEntity(const Sink::ApplicationDomain::Mail & | |||
114 | { | 151 | { |
115 | updatedIndexedProperties(newMail, newMail.getMimeMessage()); | 152 | updatedIndexedProperties(newMail, newMail.getMimeMessage()); |
116 | } | 153 | } |
117 | |||
diff --git a/common/mailpreprocessor.h b/common/mailpreprocessor.h index d2e79ca..c0eacaf 100644 --- a/common/mailpreprocessor.h +++ b/common/mailpreprocessor.h | |||
@@ -29,4 +29,3 @@ public: | |||
29 | protected: | 29 | protected: |
30 | static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, const QByteArray &data); | 30 | static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, const QByteArray &data); |
31 | }; | 31 | }; |
32 | |||
diff --git a/common/query.cpp b/common/query.cpp index 3dc8f99..5f6d095 100644 --- a/common/query.cpp +++ b/common/query.cpp | |||
@@ -34,6 +34,8 @@ QDebug operator<<(QDebug dbg, const Sink::QueryBase::Comparator &c) | |||
34 | dbg.nospace() << "contains " << c.value; | 34 | dbg.nospace() << "contains " << c.value; |
35 | } else if (c.comparator == Sink::Query::Comparator::In) { | 35 | } else if (c.comparator == Sink::Query::Comparator::In) { |
36 | dbg.nospace() << "in " << c.value; | 36 | dbg.nospace() << "in " << c.value; |
37 | } else if (c.comparator == Sink::Query::Comparator::Fulltext) { | ||
38 | dbg.nospace() << "fulltext contains " << c.value; | ||
37 | } else { | 39 | } else { |
38 | dbg.nospace() << "unknown comparator: " << c.value; | 40 | dbg.nospace() << "unknown comparator: " << c.value; |
39 | } | 41 | } |
@@ -169,6 +171,7 @@ bool QueryBase::Comparator::matches(const QVariant &v) const | |||
169 | return false; | 171 | return false; |
170 | } | 172 | } |
171 | return value.value<QByteArrayList>().contains(v.toByteArray()); | 173 | return value.value<QByteArrayList>().contains(v.toByteArray()); |
174 | case Fulltext: | ||
172 | case Invalid: | 175 | case Invalid: |
173 | default: | 176 | default: |
174 | break; | 177 | break; |
diff --git a/common/query.h b/common/query.h index 5b37cdd..1e7b41d 100644 --- a/common/query.h +++ b/common/query.h | |||
@@ -35,7 +35,8 @@ public: | |||
35 | Invalid, | 35 | Invalid, |
36 | Equals, | 36 | Equals, |
37 | Contains, | 37 | Contains, |
38 | In | 38 | In, |
39 | Fulltext | ||
39 | }; | 40 | }; |
40 | 41 | ||
41 | Comparator(); | 42 | Comparator(); |
diff --git a/common/storage/entitystore.cpp b/common/storage/entitystore.cpp index 4ad3eaf..8fbc2ad 100644 --- a/common/storage/entitystore.cpp +++ b/common/storage/entitystore.cpp | |||
@@ -162,23 +162,27 @@ void EntityStore::startTransaction(Sink::Storage::DataStore::AccessMode accessMo | |||
162 | { | 162 | { |
163 | SinkTraceCtx(d->logCtx) << "Starting transaction: " << accessMode; | 163 | SinkTraceCtx(d->logCtx) << "Starting transaction: " << accessMode; |
164 | Q_ASSERT(!d->transaction); | 164 | Q_ASSERT(!d->transaction); |
165 | Sink::Storage::DataStore store(Sink::storageLocation(), dbLayout(d->resourceContext.instanceId()), accessMode); | 165 | d->transaction = Sink::Storage::DataStore(Sink::storageLocation(), dbLayout(d->resourceContext.instanceId()), accessMode).createTransaction(accessMode); |
166 | d->transaction = store.createTransaction(accessMode); | ||
167 | } | 166 | } |
168 | 167 | ||
169 | void EntityStore::commitTransaction() | 168 | void EntityStore::commitTransaction() |
170 | { | 169 | { |
171 | SinkTraceCtx(d->logCtx) << "Committing transaction"; | 170 | SinkTraceCtx(d->logCtx) << "Committing transaction"; |
171 | |||
172 | for (const auto &type : d->indexByType.keys()) { | ||
173 | d->typeIndex(type).commitTransaction(); | ||
174 | } | ||
175 | |||
172 | Q_ASSERT(d->transaction); | 176 | Q_ASSERT(d->transaction); |
173 | d->transaction.commit(); | 177 | d->transaction.commit(); |
174 | d->transaction = Storage::DataStore::Transaction(); | 178 | d->transaction = {}; |
175 | } | 179 | } |
176 | 180 | ||
177 | void EntityStore::abortTransaction() | 181 | void EntityStore::abortTransaction() |
178 | { | 182 | { |
179 | SinkTraceCtx(d->logCtx) << "Aborting transaction"; | 183 | SinkTraceCtx(d->logCtx) << "Aborting transaction"; |
180 | d->transaction.abort(); | 184 | d->transaction.abort(); |
181 | d->transaction = Storage::DataStore::Transaction(); | 185 | d->transaction = {}; |
182 | } | 186 | } |
183 | 187 | ||
184 | bool EntityStore::hasTransaction() const | 188 | bool EntityStore::hasTransaction() const |
@@ -195,7 +199,7 @@ bool EntityStore::add(const QByteArray &type, ApplicationDomain::ApplicationDoma | |||
195 | 199 | ||
196 | SinkTraceCtx(d->logCtx) << "New entity " << entity; | 200 | SinkTraceCtx(d->logCtx) << "New entity " << entity; |
197 | 201 | ||
198 | d->typeIndex(type).add(entity.identifier(), entity, d->transaction); | 202 | d->typeIndex(type).add(entity.identifier(), entity, d->transaction, d->resourceContext.instanceId()); |
199 | 203 | ||
200 | //The maxRevision may have changed meanwhile if the entity created sub-entities | 204 | //The maxRevision may have changed meanwhile if the entity created sub-entities |
201 | const qint64 newRevision = maxRevision() + 1; | 205 | const qint64 newRevision = maxRevision() + 1; |
@@ -262,8 +266,8 @@ bool EntityStore::modify(const QByteArray &type, const ApplicationDomain::Applic | |||
262 | { | 266 | { |
263 | SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity; | 267 | SinkTraceCtx(d->logCtx) << "Modified entity: " << newEntity; |
264 | 268 | ||
265 | d->typeIndex(type).remove(current.identifier(), current, d->transaction); | 269 | d->typeIndex(type).remove(current.identifier(), current, d->transaction, d->resourceContext.instanceId()); |
266 | d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction); | 270 | d->typeIndex(type).add(newEntity.identifier(), newEntity, d->transaction, d->resourceContext.instanceId()); |
267 | 271 | ||
268 | const qint64 newRevision = DataStore::maxRevision(d->transaction) + 1; | 272 | const qint64 newRevision = DataStore::maxRevision(d->transaction) + 1; |
269 | 273 | ||
@@ -304,7 +308,7 @@ bool EntityStore::remove(const QByteArray &type, const Sink::ApplicationDomain:: | |||
304 | return false; | 308 | return false; |
305 | } | 309 | } |
306 | 310 | ||
307 | d->typeIndex(type).remove(current.identifier(), current, d->transaction); | 311 | d->typeIndex(type).remove(current.identifier(), current, d->transaction, d->resourceContext.instanceId()); |
308 | 312 | ||
309 | SinkTraceCtx(d->logCtx) << "Removed entity " << current; | 313 | SinkTraceCtx(d->logCtx) << "Removed entity " << current; |
310 | 314 | ||
@@ -422,7 +426,7 @@ QVector<QByteArray> EntityStore::indexLookup(const QByteArray &type, const Query | |||
422 | SinkTraceCtx(d->logCtx) << "Database is not existing: " << type; | 426 | SinkTraceCtx(d->logCtx) << "Database is not existing: " << type; |
423 | return QVector<QByteArray>(); | 427 | return QVector<QByteArray>(); |
424 | } | 428 | } |
425 | return d->typeIndex(type).query(query, appliedFilters, appliedSorting, d->getTransaction()); | 429 | return d->typeIndex(type).query(query, appliedFilters, appliedSorting, d->getTransaction(), d->resourceContext.instanceId()); |
426 | } | 430 | } |
427 | 431 | ||
428 | QVector<QByteArray> EntityStore::indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value) | 432 | QVector<QByteArray> EntityStore::indexLookup(const QByteArray &type, const QByteArray &property, const QVariant &value) |
diff --git a/common/typeindex.cpp b/common/typeindex.cpp index 0228ecb..f2c67a1 100644 --- a/common/typeindex.cpp +++ b/common/typeindex.cpp | |||
@@ -20,6 +20,7 @@ | |||
20 | 20 | ||
21 | #include "log.h" | 21 | #include "log.h" |
22 | #include "index.h" | 22 | #include "index.h" |
23 | #include "fulltextindex.h" | ||
23 | #include <QDateTime> | 24 | #include <QDateTime> |
24 | #include <QDataStream> | 25 | #include <QDataStream> |
25 | 26 | ||
@@ -158,7 +159,7 @@ void TypeIndex::addPropertyWithSorting<ApplicationDomain::Reference, QDateTime>( | |||
158 | addPropertyWithSorting<QByteArray, QDateTime>(property, sortProperty); | 159 | addPropertyWithSorting<QByteArray, QDateTime>(property, sortProperty); |
159 | } | 160 | } |
160 | 161 | ||
161 | void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction) | 162 | void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) |
162 | { | 163 | { |
163 | for (const auto &property : mProperties) { | 164 | for (const auto &property : mProperties) { |
164 | const auto value = entity.getProperty(property); | 165 | const auto value = entity.getProperty(property); |
@@ -172,7 +173,7 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: | |||
172 | indexer(add, identifier, value, sortValue, transaction); | 173 | indexer(add, identifier, value, sortValue, transaction); |
173 | } | 174 | } |
174 | for (const auto &indexer : mCustomIndexer) { | 175 | for (const auto &indexer : mCustomIndexer) { |
175 | indexer->setup(this, &transaction); | 176 | indexer->setup(this, &transaction, resourceInstanceId); |
176 | if (add) { | 177 | if (add) { |
177 | indexer->add(entity); | 178 | indexer->add(entity); |
178 | } else { | 179 | } else { |
@@ -182,14 +183,28 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: | |||
182 | 183 | ||
183 | } | 184 | } |
184 | 185 | ||
185 | void TypeIndex::add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction) | 186 | void TypeIndex::commitTransaction() |
186 | { | 187 | { |
187 | updateIndex(true, identifier, entity, transaction); | 188 | for (const auto &indexer : mCustomIndexer) { |
189 | indexer->commitTransaction(); | ||
190 | } | ||
188 | } | 191 | } |
189 | 192 | ||
190 | void TypeIndex::remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction) | 193 | void TypeIndex::abortTransaction() |
191 | { | 194 | { |
192 | updateIndex(false, identifier, entity, transaction); | 195 | for (const auto &indexer : mCustomIndexer) { |
196 | indexer->abortTransaction(); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | void TypeIndex::add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) | ||
201 | { | ||
202 | updateIndex(true, identifier, entity, transaction, resourceInstanceId); | ||
203 | } | ||
204 | |||
205 | void TypeIndex::remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) | ||
206 | { | ||
207 | updateIndex(false, identifier, entity, transaction, resourceInstanceId); | ||
193 | } | 208 | } |
194 | 209 | ||
195 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter) | 210 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter) |
@@ -211,13 +226,22 @@ static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filte | |||
211 | return keys; | 226 | return keys; |
212 | } | 227 | } |
213 | 228 | ||
214 | QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction) | 229 | QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId) |
215 | { | 230 | { |
216 | QVector<QByteArray> keys; | 231 | const auto baseFilters = query.getBaseFilters(); |
232 | for (auto it = baseFilters.constBegin(); it != baseFilters.constEnd(); it++) { | ||
233 | if (it.value().comparator == QueryBase::Comparator::Fulltext) { | ||
234 | FulltextIndex fulltextIndex{resourceInstanceId}; | ||
235 | const auto keys = fulltextIndex.lookup(it.value().value.toString()); | ||
236 | appliedFilters << it.key(); | ||
237 | return keys; | ||
238 | } | ||
239 | } | ||
240 | |||
217 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { | 241 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { |
218 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { | 242 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { |
219 | Index index(indexName(it.key(), it.value()), transaction); | 243 | Index index(indexName(it.key(), it.value()), transaction); |
220 | keys << indexLookup(index, query.getFilter(it.key())); | 244 | const auto keys = indexLookup(index, query.getFilter(it.key())); |
221 | appliedFilters << it.key(); | 245 | appliedFilters << it.key(); |
222 | appliedSorting = it.value(); | 246 | appliedSorting = it.value(); |
223 | SinkTraceCtx(mLogCtx) << "Index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; | 247 | SinkTraceCtx(mLogCtx) << "Index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; |
@@ -227,14 +251,14 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
227 | for (const auto &property : mProperties) { | 251 | for (const auto &property : mProperties) { |
228 | if (query.hasFilter(property)) { | 252 | if (query.hasFilter(property)) { |
229 | Index index(indexName(property), transaction); | 253 | Index index(indexName(property), transaction); |
230 | keys << indexLookup(index, query.getFilter(property)); | 254 | const auto keys = indexLookup(index, query.getFilter(property)); |
231 | appliedFilters << property; | 255 | appliedFilters << property; |
232 | SinkTraceCtx(mLogCtx) << "Index lookup on " << property << " found " << keys.size() << " keys."; | 256 | SinkTraceCtx(mLogCtx) << "Index lookup on " << property << " found " << keys.size() << " keys."; |
233 | return keys; | 257 | return keys; |
234 | } | 258 | } |
235 | } | 259 | } |
236 | SinkTraceCtx(mLogCtx) << "No matching index"; | 260 | SinkTraceCtx(mLogCtx) << "No matching index"; |
237 | return keys; | 261 | return {}; |
238 | } | 262 | } |
239 | 263 | ||
240 | QVector<QByteArray> TypeIndex::lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction) | 264 | QVector<QByteArray> TypeIndex::lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction) |
diff --git a/common/typeindex.h b/common/typeindex.h index 890c3db..b8b4d52 100644 --- a/common/typeindex.h +++ b/common/typeindex.h | |||
@@ -71,10 +71,10 @@ public: | |||
71 | mCustomIndexer << CustomIndexer::Ptr::create(); | 71 | mCustomIndexer << CustomIndexer::Ptr::create(); |
72 | } | 72 | } |
73 | 73 | ||
74 | void add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction); | 74 | void add(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
75 | void remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction); | 75 | void remove(const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
76 | 76 | ||
77 | QVector<QByteArray> query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction); | 77 | QVector<QByteArray> query(const Sink::QueryBase &query, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
78 | QVector<QByteArray> lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction); | 78 | QVector<QByteArray> lookup(const QByteArray &property, const QVariant &value, Sink::Storage::DataStore::Transaction &transaction); |
79 | 79 | ||
80 | template <typename Left, typename Right> | 80 | template <typename Left, typename Right> |
@@ -104,10 +104,13 @@ public: | |||
104 | template <typename LeftType, typename RightType> | 104 | template <typename LeftType, typename RightType> |
105 | void unindex(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction); | 105 | void unindex(const QByteArray &leftName, const QByteArray &rightName, const QVariant &leftValue, const QVariant &rightValue, Sink::Storage::DataStore::Transaction &transaction); |
106 | 106 | ||
107 | void commitTransaction(); | ||
108 | void abortTransaction(); | ||
109 | |||
107 | 110 | ||
108 | private: | 111 | private: |
109 | friend class Sink::Storage::EntityStore; | 112 | friend class Sink::Storage::EntityStore; |
110 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction); | 113 | void updateIndex(bool add, const QByteArray &identifier, const Sink::ApplicationDomain::ApplicationDomainType &entity, Sink::Storage::DataStore::Transaction &transaction, const QByteArray &resourceInstanceId); |
111 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; | 114 | QByteArray indexName(const QByteArray &property, const QByteArray &sortProperty = QByteArray()) const; |
112 | Sink::Log::Context mLogCtx; | 115 | Sink::Log::Context mLogCtx; |
113 | QByteArray mType; | 116 | QByteArray mType; |
diff --git a/synchronizer/CMakeLists.txt b/synchronizer/CMakeLists.txt index 2f8b128..9c422e6 100644 --- a/synchronizer/CMakeLists.txt +++ b/synchronizer/CMakeLists.txt | |||
@@ -10,6 +10,7 @@ add_executable(${PROJECT_NAME} ${sinksynchronizer_SRCS}) | |||
10 | target_link_libraries(${PROJECT_NAME} | 10 | target_link_libraries(${PROJECT_NAME} |
11 | sink | 11 | sink |
12 | Qt5::Core | 12 | Qt5::Core |
13 | Qt5::Gui | ||
13 | Qt5::Network | 14 | Qt5::Network |
14 | KAsync | 15 | KAsync |
15 | ${CMAKE_DL_LIBS} | 16 | ${CMAKE_DL_LIBS} |
diff --git a/synchronizer/main.cpp b/synchronizer/main.cpp index 3f79207..f4bac73 100644 --- a/synchronizer/main.cpp +++ b/synchronizer/main.cpp | |||
@@ -17,7 +17,7 @@ | |||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
18 | */ | 18 | */ |
19 | 19 | ||
20 | #include <QCoreApplication> | 20 | #include <QGuiApplication> |
21 | #include <QLockFile> | 21 | #include <QLockFile> |
22 | #include <QDir> | 22 | #include <QDir> |
23 | 23 | ||
@@ -226,7 +226,7 @@ int main(int argc, char *argv[]) | |||
226 | 226 | ||
227 | qInstallMessageHandler(qtMessageHandler); | 227 | qInstallMessageHandler(qtMessageHandler); |
228 | 228 | ||
229 | QCoreApplication app(argc, argv); | 229 | QGuiApplication app(argc, argv); |
230 | app.setQuitLockEnabled(false); | 230 | app.setQuitLockEnabled(false); |
231 | 231 | ||
232 | QByteArrayList arguments; | 232 | QByteArrayList arguments; |
diff --git a/tests/dummyresourcewritebenchmark.cpp b/tests/dummyresourcewritebenchmark.cpp index 07f57f6..e0ec503 100644 --- a/tests/dummyresourcewritebenchmark.cpp +++ b/tests/dummyresourcewritebenchmark.cpp | |||
@@ -20,6 +20,7 @@ | |||
20 | #include "hawd/formatter.h" | 20 | #include "hawd/formatter.h" |
21 | 21 | ||
22 | #include "event_generated.h" | 22 | #include "event_generated.h" |
23 | #include "mail_generated.h" | ||
23 | #include "entity_generated.h" | 24 | #include "entity_generated.h" |
24 | #include "metadata_generated.h" | 25 | #include "metadata_generated.h" |
25 | #include "createentity_generated.h" | 26 | #include "createentity_generated.h" |
@@ -27,38 +28,36 @@ | |||
27 | #include "getrssusage.h" | 28 | #include "getrssusage.h" |
28 | #include "utils.h" | 29 | #include "utils.h" |
29 | 30 | ||
31 | #include <KMime/Message> | ||
32 | |||
30 | static QByteArray createEntityBuffer(size_t attachmentSize, int &bufferSize) | 33 | static QByteArray createEntityBuffer(size_t attachmentSize, int &bufferSize) |
31 | { | 34 | { |
32 | uint8_t rawData[attachmentSize]; | ||
33 | flatbuffers::FlatBufferBuilder eventFbb; | 35 | flatbuffers::FlatBufferBuilder eventFbb; |
34 | eventFbb.Clear(); | 36 | eventFbb.Clear(); |
35 | { | 37 | { |
36 | uint8_t *rawDataPtr = Q_NULLPTR; | ||
37 | auto data = eventFbb.CreateUninitializedVector<uint8_t>(attachmentSize, &rawDataPtr); | ||
38 | auto summary = eventFbb.CreateString("summary"); | ||
39 | Sink::ApplicationDomain::Buffer::EventBuilder eventBuilder(eventFbb); | ||
40 | eventBuilder.add_summary(summary); | ||
41 | eventBuilder.add_attachment(data); | ||
42 | auto eventLocation = eventBuilder.Finish(); | ||
43 | Sink::ApplicationDomain::Buffer::FinishEventBuffer(eventFbb, eventLocation); | ||
44 | memcpy((void *)rawDataPtr, rawData, attachmentSize); | ||
45 | } | ||
46 | 38 | ||
47 | flatbuffers::FlatBufferBuilder localFbb; | 39 | auto msg = KMime::Message::Ptr::create(); |
48 | { | 40 | msg->subject()->from7BitString("Some subject"); |
49 | auto uid = localFbb.CreateString("testuid"); | 41 | msg->setBody("This is the body now."); |
50 | auto localBuilder = Sink::ApplicationDomain::Buffer::EventBuilder(localFbb); | 42 | msg->assemble(); |
51 | localBuilder.add_uid(uid); | 43 | |
52 | auto location = localBuilder.Finish(); | 44 | const auto data = msg->encodedContent(); |
53 | Sink::ApplicationDomain::Buffer::FinishEventBuffer(localFbb, location); | 45 | |
46 | auto summary = eventFbb.CreateString("summary"); | ||
47 | auto mimeMessage = eventFbb.CreateString(data.constData(), data.length()); | ||
48 | Sink::ApplicationDomain::Buffer::MailBuilder eventBuilder(eventFbb); | ||
49 | eventBuilder.add_subject(summary); | ||
50 | eventBuilder.add_messageId(summary); | ||
51 | eventBuilder.add_mimeMessage(mimeMessage); | ||
52 | Sink::ApplicationDomain::Buffer::FinishMailBuffer(eventFbb, eventBuilder.Finish()); | ||
54 | } | 53 | } |
55 | 54 | ||
56 | flatbuffers::FlatBufferBuilder entityFbb; | 55 | flatbuffers::FlatBufferBuilder entityFbb; |
57 | Sink::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize(), localFbb.GetBufferPointer(), localFbb.GetSize()); | 56 | Sink::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, eventFbb.GetBufferPointer(), eventFbb.GetSize()); |
58 | bufferSize = entityFbb.GetSize(); | 57 | bufferSize = entityFbb.GetSize(); |
59 | 58 | ||
60 | flatbuffers::FlatBufferBuilder fbb; | 59 | flatbuffers::FlatBufferBuilder fbb; |
61 | auto type = fbb.CreateString(Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Event>().toStdString().data()); | 60 | auto type = fbb.CreateString(Sink::ApplicationDomain::getTypeName<Sink::ApplicationDomain::Mail>().toStdString().data()); |
62 | auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize()); | 61 | auto delta = fbb.CreateVector<uint8_t>(entityFbb.GetBufferPointer(), entityFbb.GetSize()); |
63 | Sink::Commands::CreateEntityBuilder builder(fbb); | 62 | Sink::Commands::CreateEntityBuilder builder(fbb); |
64 | builder.add_domainType(type); | 63 | builder.add_domainType(type); |
@@ -263,10 +262,7 @@ private slots: | |||
263 | 262 | ||
264 | void runBenchmarks() | 263 | void runBenchmarks() |
265 | { | 264 | { |
266 | writeInProcess(1000, mTimeStamp); | ||
267 | writeInProcess(2000, mTimeStamp); | ||
268 | writeInProcess(5000, mTimeStamp); | 265 | writeInProcess(5000, mTimeStamp); |
269 | writeInProcess(20000, mTimeStamp); | ||
270 | } | 266 | } |
271 | 267 | ||
272 | void ensureUsedMemoryRemainsStable() | 268 | void ensureUsedMemoryRemainsStable() |
diff --git a/tests/querytest.cpp b/tests/querytest.cpp index 1584c48..ec6438d 100644 --- a/tests/querytest.cpp +++ b/tests/querytest.cpp | |||
@@ -14,6 +14,8 @@ | |||
14 | #include "testutils.h" | 14 | #include "testutils.h" |
15 | #include "applicationdomaintype.h" | 15 | #include "applicationdomaintype.h" |
16 | 16 | ||
17 | #include <KMime/Message> | ||
18 | |||
17 | using namespace Sink; | 19 | using namespace Sink; |
18 | using namespace Sink::ApplicationDomain; | 20 | using namespace Sink::ApplicationDomain; |
19 | 21 | ||
@@ -1254,6 +1256,94 @@ private slots: | |||
1254 | VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1"))); | 1256 | VERIFYEXEC(Sink::Store::removeDataFromDisk(QByteArray("sink.dummy.instance1"))); |
1255 | } | 1257 | } |
1256 | 1258 | ||
1259 | void testMailFulltextSubject() | ||
1260 | { | ||
1261 | // Setup | ||
1262 | { | ||
1263 | auto msg = KMime::Message::Ptr::create(); | ||
1264 | msg->subject()->from7BitString("Subject To Search"); | ||
1265 | msg->setBody("This is the searchable body."); | ||
1266 | msg->assemble(); | ||
1267 | { | ||
1268 | Mail mail("sink.dummy.instance1"); | ||
1269 | mail.setExtractedMessageId("test1"); | ||
1270 | mail.setExtractedSubject("Subject To Search"); | ||
1271 | mail.setFolder("folder1"); | ||
1272 | mail.setMimeMessage(msg->encodedContent()); | ||
1273 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1274 | } | ||
1275 | { | ||
1276 | Mail mail("sink.dummy.instance1"); | ||
1277 | mail.setExtractedMessageId("test2"); | ||
1278 | mail.setFolder("folder2"); | ||
1279 | mail.setExtractedSubject("Stuff"); | ||
1280 | VERIFYEXEC(Sink::Store::create<Mail>(mail)); | ||
1281 | } | ||
1282 | VERIFYEXEC(Sink::ResourceControl::flushMessageQueue("sink.dummy.instance1")); | ||
1283 | } | ||
1284 | |||
1285 | // Test | ||
1286 | { | ||
1287 | Sink::Query query; | ||
1288 | query.resourceFilter("sink.dummy.instance1"); | ||
1289 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject To Search"), QueryBase::Comparator::Fulltext)); | ||
1290 | auto model = Sink::Store::loadModel<Mail>(query); | ||
1291 | QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); | ||
1292 | QCOMPARE(model->rowCount(), 1); | ||
1293 | } | ||
1294 | { | ||
1295 | Sink::Query query; | ||
1296 | query.resourceFilter("sink.dummy.instance1"); | ||
1297 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext)); | ||
1298 | auto result = Sink::Store::read<Mail>(query); | ||
1299 | QCOMPARE(result.size(), 1); | ||
1300 | } | ||
1301 | { | ||
1302 | Sink::Query query; | ||
1303 | query.resourceFilter("sink.dummy.instance1"); | ||
1304 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("Search"), QueryBase::Comparator::Fulltext)); | ||
1305 | auto result = Sink::Store::read<Mail>(query); | ||
1306 | QCOMPARE(result.size(), 1); | ||
1307 | } | ||
1308 | { | ||
1309 | Sink::Query query; | ||
1310 | query.resourceFilter("sink.dummy.instance1"); | ||
1311 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("search"), QueryBase::Comparator::Fulltext)); | ||
1312 | auto result = Sink::Store::read<Mail>(query); | ||
1313 | QCOMPARE(result.size(), 1); | ||
1314 | } | ||
1315 | { | ||
1316 | Sink::Query query; | ||
1317 | query.resourceFilter("sink.dummy.instance1"); | ||
1318 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("sear*"), QueryBase::Comparator::Fulltext)); | ||
1319 | auto result = Sink::Store::read<Mail>(query); | ||
1320 | QCOMPARE(result.size(), 1); | ||
1321 | } | ||
1322 | { | ||
1323 | Sink::Query query; | ||
1324 | query.resourceFilter("sink.dummy.instance1"); | ||
1325 | query.filter<Mail::MimeMessage>(QueryBase::Comparator(QString("searchable"), QueryBase::Comparator::Fulltext)); | ||
1326 | auto result = Sink::Store::read<Mail>(query); | ||
1327 | QCOMPARE(result.size(), 1); | ||
1328 | } | ||
1329 | { | ||
1330 | Sink::Query query; | ||
1331 | query.resourceFilter("sink.dummy.instance1"); | ||
1332 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext)); | ||
1333 | query.filter<Mail::Folder>("folder1"); | ||
1334 | auto result = Sink::Store::read<Mail>(query); | ||
1335 | QCOMPARE(result.size(), 1); | ||
1336 | } | ||
1337 | { | ||
1338 | Sink::Query query; | ||
1339 | query.resourceFilter("sink.dummy.instance1"); | ||
1340 | query.filter<Mail::Subject>(QueryBase::Comparator(QString("Subject"), QueryBase::Comparator::Fulltext)); | ||
1341 | query.filter<Mail::Folder>("folder2"); | ||
1342 | auto result = Sink::Store::read<Mail>(query); | ||
1343 | QCOMPARE(result.size(), 0); | ||
1344 | } | ||
1345 | } | ||
1346 | |||
1257 | }; | 1347 | }; |
1258 | 1348 | ||
1259 | QTEST_MAIN(QueryTest) | 1349 | QTEST_MAIN(QueryTest) |