summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/datastorequery.cpp55
-rw-r--r--common/datastorequery.h20
-rw-r--r--common/domain/applicationdomaintype.h2
-rw-r--r--common/domain/event.cpp10
-rw-r--r--common/domain/event.h8
-rw-r--r--common/domain/folder.cpp9
-rw-r--r--common/domain/folder.h3
-rw-r--r--common/domain/mail.cpp117
-rw-r--r--common/domain/mail.fbs2
-rw-r--r--common/domain/mail.h8
-rw-r--r--common/entityreader.cpp25
-rw-r--r--common/mailpreprocessor.cpp102
-rw-r--r--common/mailpreprocessor.h3
-rw-r--r--common/query.h5
-rw-r--r--examples/maildirresource/tests/CMakeLists.txt2
-rw-r--r--examples/maildirresource/tests/data/maildir2/INBOX/cur/100000000.R28.localhost.localdomain:2,S70
-rw-r--r--examples/maildirresource/tests/data/maildir2/INBOX/cur/100000001.R28.localhost.localdomain:2,S72
-rw-r--r--examples/maildirresource/tests/maildirmailsynctest.cpp29
-rw-r--r--examples/maildirresource/tests/maildirthreadtest.cpp90
-rw-r--r--examples/maildirresource/tests/utils.h50
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/mailthreadtest.cpp94
-rw-r--r--tests/mailthreadtest.h57
23 files changed, 688 insertions, 147 deletions
diff --git a/common/datastorequery.cpp b/common/datastorequery.cpp
index 3237c53..cc070be 100644
--- a/common/datastorequery.cpp
+++ b/common/datastorequery.cpp
@@ -122,17 +122,17 @@ QVariant DataStoreQuery::getProperty(const Sink::Entity &entity, const QByteArra
122 return mGetProperty(entity, property); 122 return mGetProperty(entity, property);
123} 123}
124 124
125ResultSet DataStoreQuery::filterAndSortSet(ResultSet &resultSet, const FilterFunction &filter, bool initialQuery, const QByteArray &sortProperty) 125ResultSet DataStoreQuery::filterAndSortSet(ResultSet &resultSet, const FilterFunction &filter, const QByteArray &sortProperty)
126{ 126{
127 const bool sortingRequired = !sortProperty.isEmpty(); 127 const bool sortingRequired = !sortProperty.isEmpty();
128 if (initialQuery && sortingRequired) { 128 if (mInitialQuery && sortingRequired) {
129 SinkTrace() << "Sorting the resultset in memory according to property: " << sortProperty; 129 SinkTrace() << "Sorting the resultset in memory according to property: " << sortProperty;
130 // Sort the complete set by reading the sort property and filling into a sorted map 130 // Sort the complete set by reading the sort property and filling into a sorted map
131 auto sortedMap = QSharedPointer<QMap<QByteArray, QByteArray>>::create(); 131 auto sortedMap = QSharedPointer<QMap<QByteArray, QByteArray>>::create();
132 while (resultSet.next()) { 132 while (resultSet.next()) {
133 // readEntity is only necessary if we actually want to filter or know the operation type (but not a big deal if we do it always I guess) 133 // readEntity is only necessary if we actually want to filter or know the operation type (but not a big deal if we do it always I guess)
134 readEntity(resultSet.id(), 134 readEntity(resultSet.id(),
135 [this, filter, initialQuery, sortedMap, sortProperty, &resultSet](const QByteArray &uid, const Sink::EntityBuffer &buffer) { 135 [this, filter, sortedMap, sortProperty, &resultSet](const QByteArray &uid, const Sink::EntityBuffer &buffer) {
136 136
137 const auto operation = buffer.operation(); 137 const auto operation = buffer.operation();
138 138
@@ -154,10 +154,10 @@ ResultSet DataStoreQuery::filterAndSortSet(ResultSet &resultSet, const FilterFun
154 154
155 SinkTrace() << "Sorted " << sortedMap->size() << " values."; 155 SinkTrace() << "Sorted " << sortedMap->size() << " values.";
156 auto iterator = QSharedPointer<QMapIterator<QByteArray, QByteArray>>::create(*sortedMap); 156 auto iterator = QSharedPointer<QMapIterator<QByteArray, QByteArray>>::create(*sortedMap);
157 ResultSet::ValueGenerator generator = [this, iterator, sortedMap, filter, initialQuery]( 157 ResultSet::ValueGenerator generator = [this, iterator, sortedMap, filter](
158 std::function<void(const QByteArray &uid, const Sink::EntityBuffer &entity, Sink::Operation)> callback) -> bool { 158 std::function<void(const QByteArray &uid, const Sink::EntityBuffer &entity, Sink::Operation)> callback) -> bool {
159 if (iterator->hasNext()) { 159 if (iterator->hasNext()) {
160 readEntity(iterator->next().value(), [this, filter, callback, initialQuery](const QByteArray &uid, const Sink::EntityBuffer &buffer) { 160 readEntity(iterator->next().value(), [this, filter, callback](const QByteArray &uid, const Sink::EntityBuffer &buffer) {
161 callback(uid, buffer, Sink::Operation_Creation); 161 callback(uid, buffer, Sink::Operation_Creation);
162 }); 162 });
163 return true; 163 return true;
@@ -173,13 +173,13 @@ ResultSet DataStoreQuery::filterAndSortSet(ResultSet &resultSet, const FilterFun
173 return ResultSet(generator, skip); 173 return ResultSet(generator, skip);
174 } else { 174 } else {
175 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet); 175 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);
176 ResultSet::ValueGenerator generator = [this, resultSetPtr, filter, initialQuery](const ResultSet::Callback &callback) -> bool { 176 ResultSet::ValueGenerator generator = [this, resultSetPtr, filter](const ResultSet::Callback &callback) -> bool {
177 if (resultSetPtr->next()) { 177 if (resultSetPtr->next()) {
178 SinkTrace() << "Reading the next value: " << resultSetPtr->id(); 178 SinkTrace() << "Reading the next value: " << resultSetPtr->id();
179 // readEntity is only necessary if we actually want to filter or know the operation type (but not a big deal if we do it always I guess) 179 // readEntity is only necessary if we actually want to filter or know the operation type (but not a big deal if we do it always I guess)
180 readEntity(resultSetPtr->id(), [this, filter, callback, initialQuery](const QByteArray &uid, const Sink::EntityBuffer &buffer) { 180 readEntity(resultSetPtr->id(), [this, filter, callback](const QByteArray &uid, const Sink::EntityBuffer &buffer) {
181 const auto operation = buffer.operation(); 181 const auto operation = buffer.operation();
182 if (initialQuery) { 182 if (mInitialQuery) {
183 // We're not interested in removals during the initial query 183 // We're not interested in removals during the initial query
184 if ((operation != Sink::Operation_Removal) && filter(uid, buffer)) { 184 if ((operation != Sink::Operation_Removal) && filter(uid, buffer)) {
185 // In the initial set every entity is new 185 // In the initial set every entity is new
@@ -225,22 +225,53 @@ DataStoreQuery::FilterFunction DataStoreQuery::getFilter(const QSet<QByteArray>
225 }; 225 };
226} 226}
227 227
228ResultSet DataStoreQuery::createFilteredSet(ResultSet &resultSet, const std::function<bool(const QByteArray &, const Sink::EntityBuffer &buffer)> &filter)
229{
230 auto resultSetPtr = QSharedPointer<ResultSet>::create(resultSet);
231 ResultSet::ValueGenerator generator = [this, resultSetPtr, filter](const ResultSet::Callback &callback) -> bool {
232 return resultSetPtr->next([=](const QByteArray &uid, const Sink::EntityBuffer &buffer, Sink::Operation operation) {
233 if (mInitialQuery) {
234 // We're not interested in removals during the initial query
235 if ((operation != Sink::Operation_Removal) && filter(uid, buffer)) {
236 // In the initial set every entity is new
237 callback(uid, buffer, Sink::Operation_Creation);
238 }
239 } else {
240 // Always remove removals, they probably don't match due to non-available properties
241 if ((operation == Sink::Operation_Removal) || filter(uid, buffer)) {
242 // TODO only replay if this is in the currently visible set (or just always replay, worst case we have a couple to many results)
243 callback(uid, buffer, operation);
244 }
245 }
246 });
247 };
248 auto skip = [resultSetPtr]() { resultSetPtr->skip(1); };
249 return ResultSet(generator, skip);
250}
251
252ResultSet DataStoreQuery::postSortFilter(ResultSet &resultSet)
253{
254 return resultSet;
255}
256
228ResultSet DataStoreQuery::update(qint64 baseRevision) 257ResultSet DataStoreQuery::update(qint64 baseRevision)
229{ 258{
230 SinkTrace() << "Executing query update"; 259 SinkTrace() << "Executing query update";
260 mInitialQuery = false;
231 QSet<QByteArray> remainingFilters; 261 QSet<QByteArray> remainingFilters;
232 QByteArray remainingSorting; 262 QByteArray remainingSorting;
233 auto resultSet = loadIncrementalResultSet(baseRevision, remainingFilters); 263 auto resultSet = loadIncrementalResultSet(baseRevision, remainingFilters);
234 auto filteredSet = filterAndSortSet(resultSet, getFilter(remainingFilters), false, remainingSorting); 264 auto filteredSet = filterAndSortSet(resultSet, getFilter(remainingFilters), remainingSorting);
235 return filteredSet; 265 return postSortFilter(filteredSet);
236} 266}
237 267
238ResultSet DataStoreQuery::execute() 268ResultSet DataStoreQuery::execute()
239{ 269{
240 SinkTrace() << "Executing query"; 270 SinkTrace() << "Executing query";
271 mInitialQuery = true;
241 QSet<QByteArray> remainingFilters; 272 QSet<QByteArray> remainingFilters;
242 QByteArray remainingSorting; 273 QByteArray remainingSorting;
243 auto resultSet = loadInitialResultSet(remainingFilters, remainingSorting); 274 auto resultSet = loadInitialResultSet(remainingFilters, remainingSorting);
244 auto filteredSet = filterAndSortSet(resultSet, getFilter(remainingFilters), true, remainingSorting); 275 auto filteredSet = filterAndSortSet(resultSet, getFilter(remainingFilters), remainingSorting);
245 return filteredSet; 276 return postSortFilter(filteredSet);
246} 277}
diff --git a/common/datastorequery.h b/common/datastorequery.h
index cf9d9e2..7712ac7 100644
--- a/common/datastorequery.h
+++ b/common/datastorequery.h
@@ -27,24 +27,29 @@
27 27
28class DataStoreQuery { 28class DataStoreQuery {
29public: 29public:
30 typedef QSharedPointer<DataStoreQuery> Ptr;
31
30 DataStoreQuery(const Sink::Query &query, const QByteArray &type, Sink::Storage::Transaction &transaction, TypeIndex &typeIndex, std::function<QVariant(const Sink::Entity &entity, const QByteArray &property)> getProperty); 32 DataStoreQuery(const Sink::Query &query, const QByteArray &type, Sink::Storage::Transaction &transaction, TypeIndex &typeIndex, std::function<QVariant(const Sink::Entity &entity, const QByteArray &property)> getProperty);
31 ResultSet execute(); 33 ResultSet execute();
32 ResultSet update(qint64 baseRevision); 34 ResultSet update(qint64 baseRevision);
33 35
34private: 36protected:
35 37
36 typedef std::function<bool(const QByteArray &uid, const Sink::EntityBuffer &entityBuffer)> FilterFunction; 38 typedef std::function<bool(const QByteArray &uid, const Sink::EntityBuffer &entityBuffer)> FilterFunction;
37 typedef std::function<void(const QByteArray &uid, const Sink::EntityBuffer &entityBuffer)> BufferCallback; 39 typedef std::function<void(const QByteArray &uid, const Sink::EntityBuffer &entityBuffer)> BufferCallback;
38 40
39 QVariant getProperty(const Sink::Entity &entity, const QByteArray &property); 41 virtual QVariant getProperty(const Sink::Entity &entity, const QByteArray &property);
42
43 virtual void readEntity(const QByteArray &key, const BufferCallback &resultCallback);
40 44
41 void readEntity(const QByteArray &key, const BufferCallback &resultCallback); 45 virtual ResultSet loadInitialResultSet(QSet<QByteArray> &remainingFilters, QByteArray &remainingSorting);
46 virtual ResultSet loadIncrementalResultSet(qint64 baseRevision, QSet<QByteArray> &remainingFilters);
42 47
43 ResultSet loadInitialResultSet(QSet<QByteArray> &remainingFilters, QByteArray &remainingSorting); 48 virtual ResultSet filterAndSortSet(ResultSet &resultSet, const FilterFunction &filter, const QByteArray &sortProperty);
44 ResultSet loadIncrementalResultSet(qint64 baseRevision, QSet<QByteArray> &remainingFilters); 49 virtual ResultSet postSortFilter(ResultSet &resultSet);
50 virtual FilterFunction getFilter(const QSet<QByteArray> &remainingFilters);
45 51
46 ResultSet filterAndSortSet(ResultSet &resultSet, const FilterFunction &filter, bool initialQuery, const QByteArray &sortProperty); 52 ResultSet createFilteredSet(ResultSet &resultSet, const std::function<bool(const QByteArray &, const Sink::EntityBuffer &buffer)> &);
47 FilterFunction getFilter(const QSet<QByteArray> &remainingFilters);
48 53
49 Sink::Query mQuery; 54 Sink::Query mQuery;
50 Sink::Storage::Transaction &mTransaction; 55 Sink::Storage::Transaction &mTransaction;
@@ -52,6 +57,7 @@ private:
52 TypeIndex &mTypeIndex; 57 TypeIndex &mTypeIndex;
53 Sink::Storage::NamedDatabase mDb; 58 Sink::Storage::NamedDatabase mDb;
54 std::function<QVariant(const Sink::Entity &entity, const QByteArray &property)> mGetProperty; 59 std::function<QVariant(const Sink::Entity &entity, const QByteArray &property)> mGetProperty;
60 bool mInitialQuery;
55}; 61};
56 62
57 63
diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h
index 67f33c8..c853397 100644
--- a/common/domain/applicationdomaintype.h
+++ b/common/domain/applicationdomaintype.h
@@ -238,6 +238,8 @@ struct SINK_EXPORT Mail : public Entity {
238 SINK_PROPERTY(bool, Draft, draft); 238 SINK_PROPERTY(bool, Draft, draft);
239 SINK_PROPERTY(bool, Trash, trash); 239 SINK_PROPERTY(bool, Trash, trash);
240 SINK_PROPERTY(bool, Sent, sent); 240 SINK_PROPERTY(bool, Sent, sent);
241 SINK_EXTRACTED_PROPERTY(QByteArray, MessageId, messageId);
242 SINK_EXTRACTED_PROPERTY(QByteArray, ParentMessageId, parentMessageId);
241}; 243};
242 244
243/** 245/**
diff --git a/common/domain/event.cpp b/common/domain/event.cpp
index dfbcb61..118ffa3 100644
--- a/common/domain/event.cpp
+++ b/common/domain/event.cpp
@@ -52,11 +52,6 @@ static TypeIndex &getIndex()
52 return *index; 52 return *index;
53} 53}
54 54
55ResultSet TypeImplementation<Event>::queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction)
56{
57 return getIndex().query(query, appliedFilters, appliedSorting, transaction);
58}
59
60void TypeImplementation<Event>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 55void TypeImplementation<Event>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
61{ 56{
62 return getIndex().add(identifier, bufferAdaptor, transaction); 57 return getIndex().add(identifier, bufferAdaptor, transaction);
@@ -87,11 +82,10 @@ QSharedPointer<WritePropertyMapper<TypeImplementation<Event>::BufferBuilder> > T
87 return propertyMapper; 82 return propertyMapper;
88} 83}
89 84
90DataStoreQuery TypeImplementation<Event>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction) 85DataStoreQuery::Ptr TypeImplementation<Event>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction)
91{ 86{
92
93 auto mapper = initializeReadPropertyMapper(); 87 auto mapper = initializeReadPropertyMapper();
94 return DataStoreQuery(query, ApplicationDomain::getTypeName<Event>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) { 88 return DataStoreQuery::Ptr::create(query, ApplicationDomain::getTypeName<Event>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) {
95 89
96 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local()); 90 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local());
97 return mapper->getProperty(property, localBuffer); 91 return mapper->getProperty(property, localBuffer);
diff --git a/common/domain/event.h b/common/domain/event.h
index 4ac572c..e1ca061 100644
--- a/common/domain/event.h
+++ b/common/domain/event.h
@@ -51,13 +51,7 @@ public:
51 typedef Sink::ApplicationDomain::Buffer::Event Buffer; 51 typedef Sink::ApplicationDomain::Buffer::Event Buffer;
52 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder; 52 typedef Sink::ApplicationDomain::Buffer::EventBuilder BufferBuilder;
53 static QSet<QByteArray> indexedProperties(); 53 static QSet<QByteArray> indexedProperties();
54 static DataStoreQuery prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction); 54 static DataStoreQuery::Ptr prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction);
55 /**
56 * Returns the potential result set based on the indexes.
57 *
58 * An empty result set indicates that a full scan is required.
59 */
60 static ResultSet queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction);
61 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 55 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
62 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 56 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
63 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper(); 57 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper();
diff --git a/common/domain/folder.cpp b/common/domain/folder.cpp
index 6d487b1..17d9f13 100644
--- a/common/domain/folder.cpp
+++ b/common/domain/folder.cpp
@@ -55,11 +55,6 @@ static TypeIndex &getIndex()
55 return *index; 55 return *index;
56} 56}
57 57
58ResultSet TypeImplementation<Folder>::queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction)
59{
60 return getIndex().query(query, appliedFilters, appliedSorting, transaction);
61}
62
63void TypeImplementation<Folder>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 58void TypeImplementation<Folder>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
64{ 59{
65 SinkTrace() << "Indexing " << identifier; 60 SinkTrace() << "Indexing " << identifier;
@@ -91,10 +86,10 @@ QSharedPointer<WritePropertyMapper<TypeImplementation<Folder>::BufferBuilder> >
91 return propertyMapper; 86 return propertyMapper;
92} 87}
93 88
94DataStoreQuery TypeImplementation<Folder>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction) 89DataStoreQuery::Ptr TypeImplementation<Folder>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction)
95{ 90{
96 auto mapper = initializeReadPropertyMapper(); 91 auto mapper = initializeReadPropertyMapper();
97 return DataStoreQuery(query, ApplicationDomain::getTypeName<Folder>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) { 92 return DataStoreQuery::Ptr::create(query, ApplicationDomain::getTypeName<Folder>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) {
98 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local()); 93 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local());
99 return mapper->getProperty(property, localBuffer); 94 return mapper->getProperty(property, localBuffer);
100 }); 95 });
diff --git a/common/domain/folder.h b/common/domain/folder.h
index 77edc8a..ff87006 100644
--- a/common/domain/folder.h
+++ b/common/domain/folder.h
@@ -45,9 +45,8 @@ class TypeImplementation<Sink::ApplicationDomain::Folder> {
45public: 45public:
46 typedef Sink::ApplicationDomain::Buffer::Folder Buffer; 46 typedef Sink::ApplicationDomain::Buffer::Folder Buffer;
47 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder; 47 typedef Sink::ApplicationDomain::Buffer::FolderBuilder BufferBuilder;
48 static DataStoreQuery prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction); 48 static DataStoreQuery::Ptr prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction);
49 static QSet<QByteArray> indexedProperties(); 49 static QSet<QByteArray> indexedProperties();
50 static ResultSet queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction);
51 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 50 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
52 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 51 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
53 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper(); 52 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper();
diff --git a/common/domain/mail.cpp b/common/domain/mail.cpp
index bb5ad58..859ebef 100644
--- a/common/domain/mail.cpp
+++ b/common/domain/mail.cpp
@@ -41,6 +41,7 @@ SINK_DEBUG_AREA("mail");
41 41
42static QMutex sMutex; 42static QMutex sMutex;
43 43
44using namespace Sink;
44using namespace Sink::ApplicationDomain; 45using namespace Sink::ApplicationDomain;
45 46
46static TypeIndex &getIndex() 47static TypeIndex &getIndex()
@@ -49,26 +50,54 @@ static TypeIndex &getIndex()
49 static TypeIndex *index = 0; 50 static TypeIndex *index = 0;
50 if (!index) { 51 if (!index) {
51 index = new TypeIndex("mail"); 52 index = new TypeIndex("mail");
52 index->addProperty<QByteArray>("uid"); 53 index->addProperty<QByteArray>(Mail::Uid::name);
53 index->addProperty<QByteArray>("sender"); 54 index->addProperty<QByteArray>(Mail::Sender::name);
54 index->addProperty<QByteArray>("senderName"); 55 index->addProperty<QByteArray>(Mail::SenderName::name);
55 index->addProperty<QString>("subject"); 56 index->addProperty<QString>(Mail::Subject::name);
56 index->addProperty<QDateTime>("date"); 57 index->addProperty<QDateTime>(Mail::Date::name);
57 index->addProperty<QByteArray>("folder"); 58 index->addProperty<QByteArray>(Mail::Folder::name);
58 index->addPropertyWithSorting<QByteArray, QDateTime>("folder", "date"); 59 index->addPropertyWithSorting<QByteArray, QDateTime>(Mail::Folder::name, Mail::Date::name);
60 index->addProperty<QByteArray>(Mail::MessageId::name);
61 index->addProperty<QByteArray>(Mail::ParentMessageId::name);
59 } 62 }
60 return *index; 63 return *index;
61} 64}
62 65
63ResultSet TypeImplementation<Mail>::queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction) 66static void updateThreadingIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
64{ 67{
65 return getIndex().query(query, appliedFilters, appliedSorting, transaction); 68 auto messageId = bufferAdaptor.getProperty(Mail::MessageId::name).toByteArray();
69 auto parentMessageId = bufferAdaptor.getProperty(Mail::ParentMessageId::name).toByteArray();
70
71 Index msgIdIndex("msgId", transaction);
72 Index msgIdThreadIdIndex("msgIdThreadId", transaction);
73
74 //Add the message to the index
75 Q_ASSERT(msgIdIndex.lookup(messageId).isEmpty());
76 msgIdIndex.add(messageId, identifier);
77
78 //If parent is already available, add to thread of parent
79 QByteArray thread;
80 if (!parentMessageId.isEmpty() && !msgIdIndex.lookup(parentMessageId).isEmpty()) {
81 thread = msgIdThreadIdIndex.lookup(parentMessageId);
82 msgIdThreadIdIndex.add(messageId, thread);
83 } else {
84 thread = QUuid::createUuid().toByteArray();
85 if (!parentMessageId.isEmpty()) {
86 //Register parent with thread for when it becomes available
87 msgIdThreadIdIndex.add(parentMessageId, thread);
88 }
89 }
90 Q_ASSERT(!thread.isEmpty());
91 msgIdThreadIdIndex.add(messageId, thread);
92
93 //Look for parentMessageId and resolve to local id if available
66} 94}
67 95
68void TypeImplementation<Mail>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 96void TypeImplementation<Mail>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
69{ 97{
70 SinkTrace() << "Indexing " << identifier; 98 SinkTrace() << "Indexing " << identifier;
71 getIndex().add(identifier, bufferAdaptor, transaction); 99 getIndex().add(identifier, bufferAdaptor, transaction);
100 updateThreadingIndex(identifier, bufferAdaptor, transaction);
72} 101}
73 102
74void TypeImplementation<Mail>::removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 103void TypeImplementation<Mail>::removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
@@ -91,6 +120,8 @@ QSharedPointer<ReadPropertyMapper<TypeImplementation<Mail>::Buffer> > TypeImplem
91 propertyMapper->addMapping<Mail::Draft, Buffer>(&Buffer::draft); 120 propertyMapper->addMapping<Mail::Draft, Buffer>(&Buffer::draft);
92 propertyMapper->addMapping<Mail::Trash, Buffer>(&Buffer::trash); 121 propertyMapper->addMapping<Mail::Trash, Buffer>(&Buffer::trash);
93 propertyMapper->addMapping<Mail::Sent, Buffer>(&Buffer::sent); 122 propertyMapper->addMapping<Mail::Sent, Buffer>(&Buffer::sent);
123 propertyMapper->addMapping<Mail::MessageId, Buffer>(&Buffer::messageId);
124 propertyMapper->addMapping<Mail::ParentMessageId, Buffer>(&Buffer::parentMessageId);
94 return propertyMapper; 125 return propertyMapper;
95} 126}
96 127
@@ -110,16 +141,72 @@ QSharedPointer<WritePropertyMapper<TypeImplementation<Mail>::BufferBuilder> > Ty
110 propertyMapper->addMapping<Mail::Draft>(&BufferBuilder::add_draft); 141 propertyMapper->addMapping<Mail::Draft>(&BufferBuilder::add_draft);
111 propertyMapper->addMapping<Mail::Trash>(&BufferBuilder::add_trash); 142 propertyMapper->addMapping<Mail::Trash>(&BufferBuilder::add_trash);
112 propertyMapper->addMapping<Mail::Sent>(&BufferBuilder::add_sent); 143 propertyMapper->addMapping<Mail::Sent>(&BufferBuilder::add_sent);
144 propertyMapper->addMapping<Mail::MessageId>(&BufferBuilder::add_messageId);
145 propertyMapper->addMapping<Mail::ParentMessageId>(&BufferBuilder::add_parentMessageId);
113 return propertyMapper; 146 return propertyMapper;
114} 147}
115 148
116DataStoreQuery TypeImplementation<Mail>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction) 149class ThreadedDataStoreQuery : public DataStoreQuery
150{
151public:
152 typedef QSharedPointer<ThreadedDataStoreQuery> Ptr;
153 using DataStoreQuery::DataStoreQuery;
154
155protected:
156 ResultSet postSortFilter(ResultSet &resultSet) Q_DECL_OVERRIDE
157 {
158 auto query = mQuery;
159 if (query.threadLeaderOnly) {
160 auto rootCollection = QSharedPointer<QMap<QByteArray, QDateTime>>::create();
161 auto filter = [this, query, rootCollection](const QByteArray &uid, const Sink::EntityBuffer &entity) -> bool {
162 //TODO lookup thread
163 //if we got thread already in the result set compare dates and if newer replace
164 //else insert
165
166 const auto messageId = getProperty(entity.entity(), ApplicationDomain::Mail::MessageId::name).toByteArray();
167
168 Index msgIdIndex("msgId", mTransaction);
169 Index msgIdThreadIdIndex("msgIdThreadId", mTransaction);
170 auto thread = msgIdThreadIdIndex.lookup(messageId);
171 SinkTrace() << "MsgId: " << messageId << " Thread: " << thread << getProperty(entity.entity(), ApplicationDomain::Mail::Date::name).toDateTime();
172
173 if (rootCollection->contains(thread)) {
174 auto date = rootCollection->value(thread);
175 //The mail we have in our result already is newer, so we can ignore this one
176 if (date > getProperty(entity.entity(), ApplicationDomain::Mail::Date::name).toDateTime()) {
177 return false;
178 }
179 qWarning() << "############################################################################";
180 qWarning() << "Found a newer mail, remove the old one";
181 qWarning() << "############################################################################";
182 }
183 rootCollection->insert(thread, getProperty(entity.entity(), ApplicationDomain::Mail::Date::name).toDateTime());
184 return true;
185 };
186 return createFilteredSet(resultSet, filter);
187 } else {
188 return resultSet;
189 }
190 }
191};
192
193DataStoreQuery::Ptr TypeImplementation<Mail>::prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction)
117{ 194{
118 auto mapper = initializeReadPropertyMapper(); 195 if (query.threadLeaderOnly) {
119 return DataStoreQuery(query, ApplicationDomain::getTypeName<Mail>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) { 196 auto mapper = initializeReadPropertyMapper();
197 return ThreadedDataStoreQuery::Ptr::create(query, ApplicationDomain::getTypeName<Mail>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) {
198
199 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local());
200 return mapper->getProperty(property, localBuffer);
201 });
120 202
121 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local()); 203 } else {
122 return mapper->getProperty(property, localBuffer); 204 auto mapper = initializeReadPropertyMapper();
123 }); 205 return DataStoreQuery::Ptr::create(query, ApplicationDomain::getTypeName<Mail>(), transaction, getIndex(), [mapper](const Sink::Entity &entity, const QByteArray &property) {
206
207 const auto localBuffer = Sink::EntityBuffer::readBuffer<Buffer>(entity.local());
208 return mapper->getProperty(property, localBuffer);
209 });
210 }
124} 211}
125 212
diff --git a/common/domain/mail.fbs b/common/domain/mail.fbs
index a0c0d82..f14e9f1 100644
--- a/common/domain/mail.fbs
+++ b/common/domain/mail.fbs
@@ -13,6 +13,8 @@ table Mail {
13 draft:bool = false; 13 draft:bool = false;
14 trash:bool = false; 14 trash:bool = false;
15 sent:bool = false; 15 sent:bool = false;
16 messageId:string;
17 parentMessageId:string;
16} 18}
17 19
18root_type Mail; 20root_type Mail;
diff --git a/common/domain/mail.h b/common/domain/mail.h
index d6af9c5..3b0e9da 100644
--- a/common/domain/mail.h
+++ b/common/domain/mail.h
@@ -45,14 +45,8 @@ class TypeImplementation<Sink::ApplicationDomain::Mail> {
45public: 45public:
46 typedef Sink::ApplicationDomain::Buffer::Mail Buffer; 46 typedef Sink::ApplicationDomain::Buffer::Mail Buffer;
47 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder; 47 typedef Sink::ApplicationDomain::Buffer::MailBuilder BufferBuilder;
48 static DataStoreQuery prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction); 48 static DataStoreQuery::Ptr prepareQuery(const Sink::Query &query, Sink::Storage::Transaction &transaction);
49 static QSet<QByteArray> indexedProperties(); 49 static QSet<QByteArray> indexedProperties();
50 /**
51 * Returns the potential result set based on the indexes.
52 *
53 * An empty result set indicates that a full scan is required.
54 */
55 static ResultSet queryIndexes(const Sink::Query &query, const QByteArray &resourceInstanceIdentifier, QSet<QByteArray> &appliedFilters, QByteArray &appliedSorting, Sink::Storage::Transaction &transaction);
56 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 50 static void index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
57 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction); 51 static void removeIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction);
58 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper(); 52 static QSharedPointer<ReadPropertyMapper<Buffer> > initializeReadPropertyMapper();
diff --git a/common/entityreader.cpp b/common/entityreader.cpp
index faa154b..d86f4a9 100644
--- a/common/entityreader.cpp
+++ b/common/entityreader.cpp
@@ -150,27 +150,6 @@ void EntityReader<DomainType>::query(const Sink::Query &query, const std::functi
150 }); 150 });
151} 151}
152 152
153/* template <class DomainType> */
154/* void EntityReader<DomainType>::readEntity(const Sink::Storage::NamedDatabase &db, const QByteArray &key, */
155/* const std::function<void(const Sink::ApplicationDomain::ApplicationDomainType::Ptr &, Sink::Operation)> &resultCallback) */
156/* { */
157/* db.findLatest(key, */
158/* [=](const QByteArray &key, const QByteArray &value) -> bool { */
159/* Sink::EntityBuffer buffer(value.data(), value.size()); */
160/* const Sink::Entity &entity = buffer.entity(); */
161/* const auto metadataBuffer = Sink::EntityBuffer::readBuffer<Sink::Metadata>(entity.metadata()); */
162/* const qint64 revision = metadataBuffer ? metadataBuffer->revision() : -1; */
163/* const auto operation = metadataBuffer ? metadataBuffer->operation() : Sink::Operation_Creation; */
164/* auto adaptor = mDomainTypeAdaptorFactory.createAdaptor(entity); */
165/* Q_ASSERT(adaptor); */
166/* resultCallback(DomainType::Ptr::create(mResourceInstanceIdentifier, Sink::Storage::uidFromKey(key), revision, adaptor), operation); */
167/* return false; */
168/* }, */
169/* [&](const Sink::Storage::Error &error) { SinkWarning() << "Error during query: " << error.message << key; }); */
170/* } */
171
172
173
174template <class DomainType> 153template <class DomainType>
175QPair<qint64, qint64> EntityReader<DomainType>::executeInitialQuery(const Sink::Query &query, int offset, int batchsize, const std::function<bool(const typename DomainType::Ptr &value, Sink::Operation operation)> &callback) 154QPair<qint64, qint64> EntityReader<DomainType>::executeInitialQuery(const Sink::Query &query, int offset, int batchsize, const std::function<bool(const typename DomainType::Ptr &value, Sink::Operation operation)> &callback)
176{ 155{
@@ -178,7 +157,7 @@ QPair<qint64, qint64> EntityReader<DomainType>::executeInitialQuery(const Sink::
178 time.start(); 157 time.start();
179 158
180 auto preparedQuery = ApplicationDomain::TypeImplementation<DomainType>::prepareQuery(query, mTransaction); 159 auto preparedQuery = ApplicationDomain::TypeImplementation<DomainType>::prepareQuery(query, mTransaction);
181 auto resultSet = preparedQuery.execute(); 160 auto resultSet = preparedQuery->execute();
182 161
183 SinkTrace() << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 162 SinkTrace() << "Filtered set retrieved. " << Log::TraceTime(time.elapsed());
184 auto replayedEntities = replaySet(resultSet, offset, batchsize, callback); 163 auto replayedEntities = replaySet(resultSet, offset, batchsize, callback);
@@ -195,7 +174,7 @@ QPair<qint64, qint64> EntityReader<DomainType>::executeIncrementalQuery(const Si
195 const qint64 baseRevision = lastRevision + 1; 174 const qint64 baseRevision = lastRevision + 1;
196 175
197 auto preparedQuery = ApplicationDomain::TypeImplementation<DomainType>::prepareQuery(query, mTransaction); 176 auto preparedQuery = ApplicationDomain::TypeImplementation<DomainType>::prepareQuery(query, mTransaction);
198 auto resultSet = preparedQuery.update(baseRevision); 177 auto resultSet = preparedQuery->update(baseRevision);
199 178
200 SinkTrace() << "Filtered set retrieved. " << Log::TraceTime(time.elapsed()); 179 SinkTrace() << "Filtered set retrieved. " << Log::TraceTime(time.elapsed());
201 auto replayedEntities = replaySet(resultSet, 0, 0, callback); 180 auto replayedEntities = replaySet(resultSet, 0, 0, callback);
diff --git a/common/mailpreprocessor.cpp b/common/mailpreprocessor.cpp
index 2863ad4..0534338 100644
--- a/common/mailpreprocessor.cpp
+++ b/common/mailpreprocessor.cpp
@@ -36,48 +36,98 @@ QString MailPropertyExtractor::getFilePathFromMimeMessagePath(const QString &s)
36 return s; 36 return s;
37} 37}
38 38
39void MailPropertyExtractor::updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail) 39struct MimeMessageReader {
40{ 40 MimeMessageReader(const QString &mimeMessagePath)
41 const auto mimeMessagePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); 41 : f(mimeMessagePath),
42 if (mimeMessagePath.isNull()) { 42 mapped(0),
43 SinkTrace() << "No mime message"; 43 mappedSize(0)
44 return; 44 {
45 } 45 if (mimeMessagePath.isNull()) {
46 SinkTrace() << "Updating indexed properties " << mimeMessagePath; 46 SinkTrace() << "No mime message";
47 QFile f(mimeMessagePath); 47 return;
48 if (!f.open(QIODevice::ReadOnly)) { 48 }
49 SinkWarning() << "Failed to open the file: " << mimeMessagePath; 49 SinkTrace() << "Updating indexed properties " << mimeMessagePath;
50 return; 50 if (!f.open(QIODevice::ReadOnly)) {
51 } 51 SinkWarning() << "Failed to open the file: " << mimeMessagePath;
52 if (!f.size()) { 52 return;
53 SinkWarning() << "The file is empty."; 53 }
54 return; 54 if (!f.size()) {
55 SinkWarning() << "The file is empty.";
56 return;
57 }
58 mappedSize = qMin((qint64)8000, f.size());
59 mapped = f.map(0, mappedSize);
60 if (!mapped) {
61 SinkWarning() << "Failed to map the file: " << f.errorString();
62 return;
63 }
55 } 64 }
56 const auto mappedSize = qMin((qint64)8000, f.size()); 65
57 auto mapped = f.map(0, mappedSize); 66 KMime::Message::Ptr mimeMessage()
58 if (!mapped) { 67 {
59 SinkWarning() << "Failed to map the file: " << f.errorString(); 68 if (!mapped) {
60 return; 69 return KMime::Message::Ptr();
70 }
71 Q_ASSERT(mapped);
72 Q_ASSERT(mappedSize);
73 auto msg = KMime::Message::Ptr(new KMime::Message);
74 msg->setHead(KMime::CRLFtoLF(QByteArray::fromRawData(reinterpret_cast<const char*>(mapped), mappedSize)));
75 msg->parse();
76 return msg;
61 } 77 }
62 78
63 KMime::Message *msg = new KMime::Message; 79 QFile f;
64 msg->setHead(KMime::CRLFtoLF(QByteArray::fromRawData(reinterpret_cast<const char*>(mapped), mappedSize))); 80 uchar *mapped;
65 msg->parse(); 81 qint64 mappedSize;
82};
66 83
84static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, KMime::Message::Ptr msg)
85{
67 mail.setExtractedSubject(msg->subject(true)->asUnicodeString()); 86 mail.setExtractedSubject(msg->subject(true)->asUnicodeString());
68 mail.setExtractedSender(msg->from(true)->asUnicodeString()); 87 mail.setExtractedSender(msg->from(true)->asUnicodeString());
69 mail.setExtractedSenderName(msg->from(true)->asUnicodeString()); 88 mail.setExtractedSenderName(msg->from(true)->asUnicodeString());
70 mail.setExtractedDate(msg->date(true)->dateTime()); 89 mail.setExtractedDate(msg->date(true)->dateTime());
90
91 //The rest should never change, unless we didn't have the headers available initially.
92 auto messageId = msg->messageID(true)->identifier();
93
94 //Ensure the mssageId is unique.
95 //If there already is one with the same id we'd have to assign a new message id, which probably doesn't make any sense.
96
97 //The last is the parent
98 auto references = msg->references(true)->identifiers();
99
100 //The first is the parent
101 auto inReplyTo = msg->inReplyTo(true)->identifiers();
102 QByteArray parentMessageId;
103 if (!references.isEmpty()) {
104 parentMessageId = references.last();
105 //TODO we could use the rest of the references header to complete the ancestry in case we have missing parents.
106 } else {
107 if (!inReplyTo.isEmpty()) {
108 //According to RFC5256 we should ignore all but the first
109 parentMessageId = inReplyTo.first();
110 }
111 }
112
113 mail.setExtractedMessageId(messageId);
114 if (!parentMessageId.isEmpty()) {
115 mail.setExtractedParentMessageId(parentMessageId);
116 }
71} 117}
72 118
73void MailPropertyExtractor::newEntity(Sink::ApplicationDomain::Mail &mail, Sink::Storage::Transaction &transaction) 119void MailPropertyExtractor::newEntity(Sink::ApplicationDomain::Mail &mail, Sink::Storage::Transaction &transaction)
74{ 120{
75 updatedIndexedProperties(mail); 121 MimeMessageReader mimeMessageReader(getFilePathFromMimeMessagePath(mail.getMimeMessagePath()));
122 auto msg = mimeMessageReader.mimeMessage();
123 updatedIndexedProperties(mail, msg);
76} 124}
77 125
78void MailPropertyExtractor::modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail,Sink::Storage::Transaction &transaction) 126void MailPropertyExtractor::modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail,Sink::Storage::Transaction &transaction)
79{ 127{
80 updatedIndexedProperties(newMail); 128 MimeMessageReader mimeMessageReader(getFilePathFromMimeMessagePath(newMail.getMimeMessagePath()));
129 auto msg = mimeMessageReader.mimeMessage();
130 updatedIndexedProperties(newMail, msg);
81} 131}
82 132
83 133
diff --git a/common/mailpreprocessor.h b/common/mailpreprocessor.h
index 473931c..b7cd0e7 100644
--- a/common/mailpreprocessor.h
+++ b/common/mailpreprocessor.h
@@ -28,9 +28,6 @@ public:
28 virtual void modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail,Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE; 28 virtual void modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail,Sink::Storage::Transaction &transaction) Q_DECL_OVERRIDE;
29protected: 29protected:
30 virtual QString getFilePathFromMimeMessagePath(const QString &) const; 30 virtual QString getFilePathFromMimeMessagePath(const QString &) const;
31
32private:
33 void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail);
34}; 31};
35 32
36class SINK_EXPORT MimeMessageMover : public Sink::EntityPreprocessor<Sink::ApplicationDomain::Mail> 33class SINK_EXPORT MimeMessageMover : public Sink::EntityPreprocessor<Sink::ApplicationDomain::Mail>
diff --git a/common/query.h b/common/query.h
index 68463cd..0808432 100644
--- a/common/query.h
+++ b/common/query.h
@@ -195,13 +195,13 @@ public:
195 return *this; 195 return *this;
196 } 196 }
197 197
198 Query(const ApplicationDomain::Entity &value) : limit(0), liveQuery(false), synchronousQuery(false) 198 Query(const ApplicationDomain::Entity &value) : limit(0), liveQuery(false), synchronousQuery(false), threadLeaderOnly(false)
199 { 199 {
200 ids << value.identifier(); 200 ids << value.identifier();
201 resources << value.resourceInstanceIdentifier(); 201 resources << value.resourceInstanceIdentifier();
202 } 202 }
203 203
204 Query(Flags flags = Flags()) : limit(0), liveQuery(false), synchronousQuery(false) 204 Query(Flags flags = Flags()) : limit(0), liveQuery(false), synchronousQuery(false), threadLeaderOnly(false)
205 { 205 {
206 } 206 }
207 207
@@ -236,6 +236,7 @@ public:
236 int limit; 236 int limit;
237 bool liveQuery; 237 bool liveQuery;
238 bool synchronousQuery; 238 bool synchronousQuery;
239 bool threadLeaderOnly;
239}; 240};
240} 241}
241 242
diff --git a/examples/maildirresource/tests/CMakeLists.txt b/examples/maildirresource/tests/CMakeLists.txt
index d6f0fe7..b3e4050 100644
--- a/examples/maildirresource/tests/CMakeLists.txt
+++ b/examples/maildirresource/tests/CMakeLists.txt
@@ -8,6 +8,8 @@ include(SinkTest)
8auto_tests ( 8auto_tests (
9 maildirmailtest 9 maildirmailtest
10 maildirmailsynctest 10 maildirmailsynctest
11 maildirthreadtest
11) 12)
12target_link_libraries(maildirmailtest sink_resource_maildir) 13target_link_libraries(maildirmailtest sink_resource_maildir)
13target_link_libraries(maildirmailsynctest sink_resource_maildir) 14target_link_libraries(maildirmailsynctest sink_resource_maildir)
15target_link_libraries(maildirthreadtest sink_resource_maildir)
diff --git a/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000000.R28.localhost.localdomain:2,S b/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000000.R28.localhost.localdomain:2,S
new file mode 100644
index 0000000..e899eb7
--- /dev/null
+++ b/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000000.R28.localhost.localdomain:2,S
@@ -0,0 +1,70 @@
1Return-Path: <nepomuk-bounces@kde.org>
2Received: from compute4.internal (compute4.nyi.mail.srv.osa [10.202.2.44])
3 by slots3a1p1 (Cyrus git2.5+0-git-fastmail-8998) with LMTPA;
4 Mon, 11 Mar 2013 14:28:42 -0400
5X-Sieve: CMU Sieve 2.4
6X-Spam-score: 0.0
7X-Spam-hits: BAYES_00 -1.9, RCVD_IN_DNSWL_MED -2.3, RP_MATCHES_RCVD -0.704,
8 LANGUAGES unknown, BAYES_USED global, SA_VERSION 3.3.1
9X-Spam-source: IP='46.4.96.248', Host='postbox.kde.org', Country='unk', FromHeader='org',
10 MailFrom='org'
11X-Spam-charsets: plain='us-ascii'
12X-Resolved-to: chrigi_1@fastmail.fm
13X-Delivered-to: chrigi_1@fastmail.fm
14X-Mail-from: nepomuk-bounces@kde.org
15Received: from mx4.nyi.mail.srv.osa ([10.202.2.203])
16 by compute4.internal (LMTPProxy); Mon, 11 Mar 2013 14:28:42 -0400
17Received: from postbox.kde.org (postbox.kde.org [46.4.96.248])
18 by mx4.messagingengine.com (Postfix) with ESMTP id 1C9D2440F88
19 for <chrigi_1@fastmail.fm>; Mon, 11 Mar 2013 14:28:42 -0400 (EDT)
20Received: from postbox.kde.org (localhost [IPv6:::1])
21 by postbox.kde.org (Postfix) with ESMTP id 00FFEB3732B;
22 Mon, 11 Mar 2013 18:28:40 +0000 (UTC)
23DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=kde.org; s=default;
24 t=1363026520; bh=cOdvyBAJJ8ho64q0H7rxkl+cB2y6TiyVOX0fO3yZ64U=;
25 h=Date:From:To:Message-ID:In-Reply-To:References:MIME-Version:
26 Subject:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:
27 List-Subscribe:Content-Type:Content-Transfer-Encoding:Sender; b=dv
28 dJAFu+6JCuNun5WIuP4ysfKpLh0DeuhEEfy2cQavUGMICJ27k7tI73x6gN37V5Q/evJ
29 NDFna3/IhNBsAQeLiXs28HKxzcVhbnq5jdFR6fbyo6k1fOKt5vTT1GTDZ+3zIGPD1CU
30 ioDBGxPb/Ds6gee90tjadOj6o+Oc+2ZSq94=
31X-Original-To: nepomuk@kde.org
32X-Remote-Delivered-To: nepomuk@localhost.kde.org
33Received: from build.kde.org (build.kde.org [IPv6:2a01:4f8:160:9363::5])
34 by postbox.kde.org (Postfix) with ESMTP id 4491CB3732B
35 for <nepomuk@kde.org>; Mon, 11 Mar 2013 18:28:27 +0000 (UTC)
36Received: from localhost ([127.0.0.1]) by build.kde.org with esmtp (Exim 4.72)
37 (envelope-from <null@kde.org>) id 1UF7SV-0000gs-11
38 for nepomuk@kde.org; Mon, 11 Mar 2013 18:28:27 +0000
39Date: Mon, 11 Mar 2013 18:28:27 +0000 (UTC)
40From: KDE CI System <null@kde.org>
41To: nepomuk@kde.org
42Message-ID: <1977027405.27.1363026507008.JavaMail.jenkins@build>
43MIME-Version: 1.0
44X-Jenkins-Job: nepomuk-core_stable
45X-Jenkins-Result: UNSTABLE
46X-Scanned-By: MIMEDefang 2.71 on 46.4.96.248
47Subject: First message
48X-BeenThere: nepomuk@kde.org
49X-Mailman-Version: 2.1.14
50Precedence: list
51List-Id: The Semantic KDE <nepomuk.kde.org>
52List-Unsubscribe: <https://mail.kde.org/mailman/options/nepomuk>,
53 <mailto:nepomuk-request@kde.org?subject=unsubscribe>
54List-Archive: <http://mail.kde.org/pipermail/nepomuk>
55List-Post: <mailto:nepomuk@kde.org>
56List-Help: <mailto:nepomuk-request@kde.org?subject=help>
57List-Subscribe: <https://mail.kde.org/mailman/listinfo/nepomuk>,
58 <mailto:nepomuk-request@kde.org?subject=subscribe>
59Content-Type: text/plain; charset="us-ascii"
60Content-Transfer-Encoding: 7bit
61Errors-To: nepomuk-bounces@kde.org
62Sender: nepomuk-bounces@kde.org
63X-Truedomain: NotChecked
64
65See <http://build.kde.org/job/nepomuk-core_stable/changes>
66
67_______________________________________________
68Nepomuk mailing list
69Nepomuk@kde.org
70https://mail.kde.org/mailman/listinfo/nepomuk
diff --git a/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000001.R28.localhost.localdomain:2,S b/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000001.R28.localhost.localdomain:2,S
new file mode 100644
index 0000000..cfdd438
--- /dev/null
+++ b/examples/maildirresource/tests/data/maildir2/INBOX/cur/100000001.R28.localhost.localdomain:2,S
@@ -0,0 +1,72 @@
1Return-Path: <nepomuk-bounces@kde.org>
2Received: from compute4.internal (compute4.nyi.mail.srv.osa [10.202.2.44])
3 by slots3a1p1 (Cyrus git2.5+0-git-fastmail-8998) with LMTPA;
4 Mon, 11 Mar 2013 14:28:42 -0400
5X-Sieve: CMU Sieve 2.4
6X-Spam-score: 0.0
7X-Spam-hits: BAYES_00 -1.9, RCVD_IN_DNSWL_MED -2.3, RP_MATCHES_RCVD -0.704,
8 LANGUAGES unknown, BAYES_USED global, SA_VERSION 3.3.1
9X-Spam-source: IP='46.4.96.248', Host='postbox.kde.org', Country='unk', FromHeader='org',
10 MailFrom='org'
11X-Spam-charsets: plain='us-ascii'
12X-Resolved-to: chrigi_1@fastmail.fm
13X-Delivered-to: chrigi_1@fastmail.fm
14X-Mail-from: nepomuk-bounces@kde.org
15Received: from mx4.nyi.mail.srv.osa ([10.202.2.203])
16 by compute4.internal (LMTPProxy); Mon, 11 Mar 2013 14:28:42 -0400
17Received: from postbox.kde.org (postbox.kde.org [46.4.96.248])
18 by mx4.messagingengine.com (Postfix) with ESMTP id 1C9D2440F88
19 for <chrigi_1@fastmail.fm>; Mon, 11 Mar 2013 14:28:42 -0400 (EDT)
20Received: from postbox.kde.org (localhost [IPv6:::1])
21 by postbox.kde.org (Postfix) with ESMTP id 00FFEB3732B;
22 Mon, 11 Mar 2013 18:28:40 +0000 (UTC)
23DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=kde.org; s=default;
24 t=1363026520; bh=cOdvyBAJJ8ho64q0H7rxkl+cB2y6TiyVOX0fO3yZ64U=;
25 h=Date:From:To:Message-ID:In-Reply-To:References:MIME-Version:
26 Subject:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:
27 List-Subscribe:Content-Type:Content-Transfer-Encoding:Sender; b=dv
28 dJAFu+6JCuNun5WIuP4ysfKpLh0DeuhEEfy2cQavUGMICJ27k7tI73x6gN37V5Q/evJ
29 NDFna3/IhNBsAQeLiXs28HKxzcVhbnq5jdFR6fbyo6k1fOKt5vTT1GTDZ+3zIGPD1CU
30 ioDBGxPb/Ds6gee90tjadOj6o+Oc+2ZSq94=
31X-Original-To: nepomuk@kde.org
32X-Remote-Delivered-To: nepomuk@localhost.kde.org
33Received: from build.kde.org (build.kde.org [IPv6:2a01:4f8:160:9363::5])
34 by postbox.kde.org (Postfix) with ESMTP id 4491CB3732B
35 for <nepomuk@kde.org>; Mon, 11 Mar 2013 18:28:27 +0000 (UTC)
36Received: from localhost ([127.0.0.1]) by build.kde.org with esmtp (Exim 4.72)
37 (envelope-from <null@kde.org>) id 1UF7SV-0000gs-11
38 for nepomuk@kde.org; Mon, 11 Mar 2013 18:28:27 +0000
39Date: Mon, 11 Mar 2013 19:28:27 +0000 (UTC)
40From: KDE CI System <null@kde.org>
41To: nepomuk@kde.org
42Message-ID: <1000000001.27.1363026507008.JavaMail.jenkins@build>
43In-Reply-To: <1977027405.27.1363026507008.JavaMail.jenkins@build>
44References: <1977027405.27.1363026507008.JavaMail.jenkins@build>
45MIME-Version: 1.0
46X-Jenkins-Job: nepomuk-core_stable
47X-Jenkins-Result: UNSTABLE
48X-Scanned-By: MIMEDefang 2.71 on 46.4.96.248
49Subject: ThreadLeader
50X-BeenThere: nepomuk@kde.org
51X-Mailman-Version: 2.1.14
52Precedence: list
53List-Id: The Semantic KDE <nepomuk.kde.org>
54List-Unsubscribe: <https://mail.kde.org/mailman/options/nepomuk>,
55 <mailto:nepomuk-request@kde.org?subject=unsubscribe>
56List-Archive: <http://mail.kde.org/pipermail/nepomuk>
57List-Post: <mailto:nepomuk@kde.org>
58List-Help: <mailto:nepomuk-request@kde.org?subject=help>
59List-Subscribe: <https://mail.kde.org/mailman/listinfo/nepomuk>,
60 <mailto:nepomuk-request@kde.org?subject=subscribe>
61Content-Type: text/plain; charset="us-ascii"
62Content-Transfer-Encoding: 7bit
63Errors-To: nepomuk-bounces@kde.org
64Sender: nepomuk-bounces@kde.org
65X-Truedomain: NotChecked
66
67See <http://build.kde.org/job/nepomuk-core_stable/changes>
68
69_______________________________________________
70Nepomuk mailing list
71Nepomuk@kde.org
72https://mail.kde.org/mailman/listinfo/nepomuk
diff --git a/examples/maildirresource/tests/maildirmailsynctest.cpp b/examples/maildirresource/tests/maildirmailsynctest.cpp
index f2cf3e0..bafd5a8 100644
--- a/examples/maildirresource/tests/maildirmailsynctest.cpp
+++ b/examples/maildirresource/tests/maildirmailsynctest.cpp
@@ -25,36 +25,11 @@
25#include "common/test.h" 25#include "common/test.h"
26#include "common/domain/applicationdomaintype.h" 26#include "common/domain/applicationdomaintype.h"
27 27
28#include "utils.h"
29
28using namespace Sink; 30using namespace Sink;
29using namespace Sink::ApplicationDomain; 31using namespace Sink::ApplicationDomain;
30 32
31static bool copyRecursively(const QString &srcFilePath, const QString &tgtFilePath)
32{
33 QFileInfo srcFileInfo(srcFilePath);
34 if (srcFileInfo.isDir()) {
35 QDir targetDir(tgtFilePath);
36 targetDir.cdUp();
37 if (!targetDir.mkdir(QFileInfo(srcFilePath).fileName())) {
38 qWarning() << "Failed to create directory " << tgtFilePath;
39 return false;
40 }
41 QDir sourceDir(srcFilePath);
42 QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
43 foreach (const QString &fileName, fileNames) {
44 const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName;
45 const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName;
46 if (!copyRecursively(newSrcFilePath, newTgtFilePath))
47 return false;
48 }
49 } else {
50 if (!QFile::copy(srcFilePath, tgtFilePath)) {
51 qWarning() << "Failed to copy file " << tgtFilePath;
52 return false;
53 }
54 }
55 return true;
56}
57
58/** 33/**
59 * Test of complete system using the maildir resource. 34 * Test of complete system using the maildir resource.
60 * 35 *
diff --git a/examples/maildirresource/tests/maildirthreadtest.cpp b/examples/maildirresource/tests/maildirthreadtest.cpp
new file mode 100644
index 0000000..a74869c
--- /dev/null
+++ b/examples/maildirresource/tests/maildirthreadtest.cpp
@@ -0,0 +1,90 @@
1/*
2 * Copyright (C) 2016 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 <QtTest>
20
21#include <tests/mailthreadtest.h>
22#include "../maildirresource.h"
23#include "../libmaildir/maildir.h"
24
25#include "common/test.h"
26#include "common/domain/applicationdomaintype.h"
27
28#include "utils.h";
29
30using namespace Sink;
31using namespace Sink::ApplicationDomain;
32
33/**
34 * Test of complete system using the maildir resource.
35 *
36 * This test requires the maildir resource installed.
37 */
38class MaildirThreadTest : public Sink::MailThreadTest
39{
40 Q_OBJECT
41
42 QTemporaryDir tempDir;
43 QString targetPath;
44
45protected:
46 void resetTestEnvironment() Q_DECL_OVERRIDE
47 {
48 targetPath = tempDir.path() + "/maildir2/";
49 QDir dir(targetPath);
50 dir.removeRecursively();
51 copyRecursively(TESTDATAPATH "/maildir2", targetPath);
52 }
53
54 Sink::ApplicationDomain::SinkResource createResource() Q_DECL_OVERRIDE
55 {
56 auto resource = ApplicationDomain::MaildirResource::create("account1");
57 resource.setProperty("path", targetPath);
58 return resource;
59 }
60
61 Sink::ApplicationDomain::SinkResource createFaultyResource() Q_DECL_OVERRIDE
62 {
63 auto resource = ApplicationDomain::MaildirResource::create("account1");
64 resource.setProperty("path", "");
65 return resource;
66 }
67
68 void removeResourceFromDisk(const QByteArray &identifier) Q_DECL_OVERRIDE
69 {
70 ::MaildirResource::removeFromDisk(identifier);
71 }
72
73 QByteArray createMessage(const QStringList &folderPath, const QByteArray &message) Q_DECL_OVERRIDE
74 {
75 auto rootPath = tempDir.path() + "/maildir2/";
76 KPIM::Maildir maildir(rootPath + folderPath.join('/'));
77 return maildir.addEntry(message).toUtf8();
78 }
79
80 void removeMessage(const QStringList &folderPath, const QByteArray &messageIdentifier) Q_DECL_OVERRIDE
81 {
82 auto rootPath = tempDir.path() + "/maildir2/";
83 KPIM::Maildir maildir(rootPath + folderPath.join('/'));
84 maildir.removeEntry(messageIdentifier);
85 }
86};
87
88QTEST_MAIN(MaildirThreadTest)
89
90#include "maildirthreadtest.moc"
diff --git a/examples/maildirresource/tests/utils.h b/examples/maildirresource/tests/utils.h
new file mode 100644
index 0000000..6e25353
--- /dev/null
+++ b/examples/maildirresource/tests/utils.h
@@ -0,0 +1,50 @@
1/*
2 * Copyright (C) 2016 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 <QFileInfo>
22#include <QDir>
23#include <QDebug>
24
25static bool copyRecursively(const QString &srcFilePath, const QString &tgtFilePath)
26{
27 QFileInfo srcFileInfo(srcFilePath);
28 if (srcFileInfo.isDir()) {
29 QDir targetDir(tgtFilePath);
30 targetDir.cdUp();
31 if (!targetDir.mkdir(QFileInfo(srcFilePath).fileName())) {
32 qWarning() << "Failed to create directory " << tgtFilePath;
33 return false;
34 }
35 QDir sourceDir(srcFilePath);
36 QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
37 foreach (const QString &fileName, fileNames) {
38 const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName;
39 const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName;
40 if (!copyRecursively(newSrcFilePath, newTgtFilePath))
41 return false;
42 }
43 } else {
44 if (!QFile::copy(srcFilePath, tgtFilePath)) {
45 qWarning() << "Failed to copy file " << tgtFilePath;
46 return false;
47 }
48 }
49 return true;
50}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 294cce2..f8fe3b3 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -12,7 +12,7 @@ add_definitions(-DTESTDATAPATH="${CMAKE_CURRENT_SOURCE_DIR}/data")
12 12
13find_package(KF5 COMPONENTS REQUIRED Mime) 13find_package(KF5 COMPONENTS REQUIRED Mime)
14 14
15add_library(sink_test SHARED testimplementations.cpp getrssusage.cpp mailtest.cpp mailsynctest.cpp) 15add_library(sink_test SHARED testimplementations.cpp getrssusage.cpp mailtest.cpp mailsynctest.cpp mailthreadtest.cpp)
16qt5_use_modules(sink_test Core Test Concurrent) 16qt5_use_modules(sink_test Core Test Concurrent)
17target_link_libraries(sink_test sink libhawd KF5::Mime) 17target_link_libraries(sink_test sink libhawd KF5::Mime)
18 18
diff --git a/tests/mailthreadtest.cpp b/tests/mailthreadtest.cpp
new file mode 100644
index 0000000..1bbe713
--- /dev/null
+++ b/tests/mailthreadtest.cpp
@@ -0,0 +1,94 @@
1/*
2 * Copyright (C) 2016 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 "mailthreadtest.h"
20
21#include <QtTest>
22
23#include <QString>
24#include <KMime/Message>
25
26#include "store.h"
27#include "resourcecontrol.h"
28#include "log.h"
29#include "test.h"
30
31using namespace Sink;
32using namespace Sink::ApplicationDomain;
33
34SINK_DEBUG_AREA("mailthreadtest")
35
36//TODO extract resource test
37//
38void MailThreadTest::initTestCase()
39{
40 Test::initTest();
41 QVERIFY(isBackendAvailable());
42 resetTestEnvironment();
43 auto resource = createResource();
44 QVERIFY(!resource.identifier().isEmpty());
45
46 VERIFYEXEC(Store::create(resource));
47
48 mResourceInstanceIdentifier = resource.identifier();
49 mCapabilities = resource.getProperty("capabilities").value<QByteArrayList>();
50}
51
52void MailThreadTest::cleanup()
53{
54 //TODO the shutdown job fails if the resource is already shut down
55 // VERIFYEXEC(ResourceControl::shutdown(mResourceInstanceIdentifier));
56 ResourceControl::shutdown(mResourceInstanceIdentifier).exec().waitForFinished();
57 removeResourceFromDisk(mResourceInstanceIdentifier);
58}
59
60void MailThreadTest::init()
61{
62 VERIFYEXEC(ResourceControl::start(mResourceInstanceIdentifier));
63}
64
65
66void MailThreadTest::testListThreadLeader()
67{
68 Sink::Query query;
69 query.resources << mResourceInstanceIdentifier;
70 query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
71 query.threadLeaderOnly = true;
72 query.sort<Mail::Date>();
73
74 // Ensure all local data is processed
75 VERIFYEXEC(Store::synchronize(query));
76 ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished();
77
78 auto job = Store::fetchAll<Mail>(query).syncThen<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) {
79 QCOMPARE(mails.size(), 1);
80 QVERIFY(mails.first()->getSubject().startsWith(QString("ThreadLeader")));
81 const auto data = mails.first()->getMimeMessage();
82 QVERIFY(!data.isEmpty());
83
84 KMime::Message m;
85 m.setContent(data);
86 m.parse();
87 QCOMPARE(mails.first()->getSubject(), m.subject(true)->asUnicodeString());
88 QVERIFY(!mails.first()->getFolder().isEmpty());
89 QVERIFY(mails.first()->getDate().isValid());
90 });
91 VERIFYEXEC(job);
92}
93
94#include "mailthreadtest.moc"
diff --git a/tests/mailthreadtest.h b/tests/mailthreadtest.h
new file mode 100644
index 0000000..d6b9c24
--- /dev/null
+++ b/tests/mailthreadtest.h
@@ -0,0 +1,57 @@
1/*
2 * Copyright (C) 2016 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 <QObject>
22#include <QByteArray>
23
24#include <applicationdomaintype.h>
25#include "testutils.h"
26
27namespace Sink {
28
29/**
30 * Tests if the resource can thread emails.
31 */
32class MailThreadTest : public QObject
33{
34 Q_OBJECT
35
36protected:
37 QByteArray mResourceInstanceIdentifier;
38 QByteArrayList mCapabilities;
39
40 virtual bool isBackendAvailable() { return true; }
41 virtual void resetTestEnvironment() = 0;
42 virtual Sink::ApplicationDomain::SinkResource createResource() = 0;
43 virtual Sink::ApplicationDomain::SinkResource createFaultyResource() = 0;
44 virtual void removeResourceFromDisk(const QByteArray &mResourceInstanceIdentifier) = 0;
45 virtual QByteArray createMessage(const QStringList &folderPath, const QByteArray &message) = 0;
46 virtual void removeMessage(const QStringList &folderPath, const QByteArray &messageIdentifier) = 0;
47
48private slots:
49 void initTestCase();
50 void init();
51 void cleanup();
52
53 void testListThreadLeader();
54};
55
56}
57