summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/domain/mail.cpp79
-rw-r--r--tests/mailthreadtest.cpp101
-rw-r--r--tests/mailthreadtest.h1
3 files changed, 165 insertions, 16 deletions
diff --git a/common/domain/mail.cpp b/common/domain/mail.cpp
index 859ebef..0c737fa 100644
--- a/common/domain/mail.cpp
+++ b/common/domain/mail.cpp
@@ -63,34 +63,98 @@ static TypeIndex &getIndex()
63 return *index; 63 return *index;
64} 64}
65 65
66static QString stripOffPrefixes(const QString &subject)
67{
68 //TODO this hardcoded list is probably not good enough (especially regarding internationalization)
69 //TODO this whole routine, including internationalized re/fwd ... should go into some library.
70 //We'll require the same for generating reply/forward subjects in kube
71 static QStringList defaultReplyPrefixes = QStringList() << QLatin1String("Re\\s*:")
72 << QLatin1String("Re\\[\\d+\\]:")
73 << QLatin1String("Re\\d+:");
74
75 static QStringList defaultForwardPrefixes = QStringList() << QLatin1String("Fwd:")
76 << QLatin1String("FW:");
77
78 QStringList replyPrefixes; // = GlobalSettings::self()->replyPrefixes();
79 if (replyPrefixes.isEmpty()) {
80 replyPrefixes = defaultReplyPrefixes;
81 }
82
83 QStringList forwardPrefixes; // = GlobalSettings::self()->forwardPrefixes();
84 if (forwardPrefixes.isEmpty()) {
85 forwardPrefixes = defaultReplyPrefixes;
86 }
87
88 const QStringList prefixRegExps = replyPrefixes + forwardPrefixes;
89
90 // construct a big regexp that
91 // 1. is anchored to the beginning of str (sans whitespace)
92 // 2. matches at least one of the part regexps in prefixRegExps
93 const QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QLatin1String(")|(?:")));
94
95 static QString regExpPattern;
96 static QRegExp regExp;
97
98 regExp.setCaseSensitivity(Qt::CaseInsensitive);
99 if (regExpPattern != bigRegExp) {
100 // the prefixes have changed, so update the regexp
101 regExpPattern = bigRegExp;
102 regExp.setPattern(regExpPattern);
103 }
104
105 if(regExp.isValid()) {
106 QString tmp = subject;
107 if (regExp.indexIn( tmp ) == 0) {
108 return tmp.remove(0, regExp.matchedLength());
109 }
110 } else {
111 SinkWarning() << "bigRegExp = \""
112 << bigRegExp << "\"\n"
113 << "prefix regexp is invalid!";
114 }
115
116 return subject;
117}
118
119
66static void updateThreadingIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 120static void updateThreadingIndex(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
67{ 121{
68 auto messageId = bufferAdaptor.getProperty(Mail::MessageId::name).toByteArray(); 122 auto messageId = bufferAdaptor.getProperty(Mail::MessageId::name).toByteArray();
69 auto parentMessageId = bufferAdaptor.getProperty(Mail::ParentMessageId::name).toByteArray(); 123 auto parentMessageId = bufferAdaptor.getProperty(Mail::ParentMessageId::name).toByteArray();
124 auto subject = bufferAdaptor.getProperty(Mail::Subject::name).toString();
70 125
71 Index msgIdIndex("msgId", transaction); 126 Index msgIdIndex("msgId", transaction);
72 Index msgIdThreadIdIndex("msgIdThreadId", transaction); 127 Index msgIdThreadIdIndex("msgIdThreadId", transaction);
128 Index subjectThreadIdIndex("subjectThreadId", transaction);
73 129
74 //Add the message to the index 130 //Add the message to the index
75 Q_ASSERT(msgIdIndex.lookup(messageId).isEmpty()); 131 Q_ASSERT(msgIdIndex.lookup(messageId).isEmpty());
76 msgIdIndex.add(messageId, identifier); 132 msgIdIndex.add(messageId, identifier);
77 133
78 //If parent is already available, add to thread of parent 134 auto normalizedSubject = stripOffPrefixes(subject).toUtf8();
135
79 QByteArray thread; 136 QByteArray thread;
137 //If parent is already available, add to thread of parent
80 if (!parentMessageId.isEmpty() && !msgIdIndex.lookup(parentMessageId).isEmpty()) { 138 if (!parentMessageId.isEmpty() && !msgIdIndex.lookup(parentMessageId).isEmpty()) {
81 thread = msgIdThreadIdIndex.lookup(parentMessageId); 139 thread = msgIdThreadIdIndex.lookup(parentMessageId);
82 msgIdThreadIdIndex.add(messageId, thread); 140 msgIdThreadIdIndex.add(messageId, thread);
141 subjectThreadIdIndex.add(normalizedSubject, thread);
83 } else { 142 } else {
84 thread = QUuid::createUuid().toByteArray(); 143 //Try to lookup the thread by subject:
85 if (!parentMessageId.isEmpty()) { 144 thread = subjectThreadIdIndex.lookup(normalizedSubject);
86 //Register parent with thread for when it becomes available 145 if (!thread.isEmpty()) {
87 msgIdThreadIdIndex.add(parentMessageId, thread); 146 msgIdThreadIdIndex.add(messageId, thread);
147 } else {
148 thread = QUuid::createUuid().toByteArray();
149 subjectThreadIdIndex.add(normalizedSubject, thread);
150 if (!parentMessageId.isEmpty()) {
151 //Register parent with thread for when it becomes available
152 msgIdThreadIdIndex.add(parentMessageId, thread);
153 }
88 } 154 }
89 } 155 }
90 Q_ASSERT(!thread.isEmpty()); 156 Q_ASSERT(!thread.isEmpty());
91 msgIdThreadIdIndex.add(messageId, thread); 157 msgIdThreadIdIndex.add(messageId, thread);
92
93 //Look for parentMessageId and resolve to local id if available
94} 158}
95 159
96void TypeImplementation<Mail>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction) 160void TypeImplementation<Mail>::index(const QByteArray &identifier, const BufferAdaptor &bufferAdaptor, Sink::Storage::Transaction &transaction)
@@ -173,6 +237,7 @@ protected:
173 if (rootCollection->contains(thread)) { 237 if (rootCollection->contains(thread)) {
174 auto date = rootCollection->value(thread); 238 auto date = rootCollection->value(thread);
175 //The mail we have in our result already is newer, so we can ignore this one 239 //The mail we have in our result already is newer, so we can ignore this one
240 //This is always true during the initial query if the set has been sorted by date.
176 if (date > getProperty(entity.entity(), ApplicationDomain::Mail::Date::name).toDateTime()) { 241 if (date > getProperty(entity.entity(), ApplicationDomain::Mail::Date::name).toDateTime()) {
177 return false; 242 return false;
178 } 243 }
diff --git a/tests/mailthreadtest.cpp b/tests/mailthreadtest.cpp
index 1bbe713..a3df56b 100644
--- a/tests/mailthreadtest.cpp
+++ b/tests/mailthreadtest.cpp
@@ -78,17 +78,100 @@ void MailThreadTest::testListThreadLeader()
78 auto job = Store::fetchAll<Mail>(query).syncThen<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) { 78 auto job = Store::fetchAll<Mail>(query).syncThen<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) {
79 QCOMPARE(mails.size(), 1); 79 QCOMPARE(mails.size(), 1);
80 QVERIFY(mails.first()->getSubject().startsWith(QString("ThreadLeader"))); 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 }); 81 });
91 VERIFYEXEC(job); 82 VERIFYEXEC(job);
92} 83}
93 84
85/*
86 * Thread:
87 * 1.
88 * 2.
89 * 3.
90 *
91 * 3. first, should result in a new thread.
92 * 1. second, should be merged by subject
93 * 2. last, should complete the thread.
94 */
95void MailThreadTest::testIndexInMixedOrder()
96{
97 auto folder = Folder::create(mResourceInstanceIdentifier);
98 folder.setName("folder");
99 VERIFYEXEC(Store::create(folder));
100
101 auto message1 = KMime::Message::Ptr::create();
102 message1->subject(true)->fromUnicodeString("1", "utf8");
103 message1->messageID(true)->generate("foobar.com");
104 message1->date(true)->setDateTime(QDateTime::currentDateTimeUtc());
105 message1->assemble();
106
107 auto message2 = KMime::Message::Ptr::create();
108 message2->subject(true)->fromUnicodeString("Re: 1", "utf8");
109 message2->messageID(true)->generate("foobar.com");
110 message2->inReplyTo(true)->appendIdentifier(message1->messageID(true)->identifier());
111 message2->date(true)->setDateTime(QDateTime::currentDateTimeUtc().addSecs(1));
112 message2->assemble();
113
114 auto message3 = KMime::Message::Ptr::create();
115 message3->subject(true)->fromUnicodeString("Re: Re: 1", "utf8");
116 message3->messageID(true)->generate("foobar.com");
117 message3->inReplyTo(true)->appendIdentifier(message2->messageID(true)->identifier());
118 message3->date(true)->setDateTime(QDateTime::currentDateTimeUtc().addSecs(2));
119 message3->assemble();
120
121 {
122 auto mail = Mail::create(mResourceInstanceIdentifier);
123 mail.setMimeMessage(message3->encodedContent());
124 mail.setFolder(folder);
125 VERIFYEXEC(Store::create(mail));
126 }
127 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
128
129 Sink::Query query;
130 query.resources << mResourceInstanceIdentifier;
131 query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
132 query.threadLeaderOnly = true;
133 query.sort<Mail::Date>();
134 query.filter<Mail::Folder>(folder);
135
136 {
137 auto job = Store::fetchAll<Mail>(query)
138 .syncThen<void, QList<Mail::Ptr>>([=](const QList<Mail::Ptr> &mails) {
139 QCOMPARE(mails.size(), 1);
140 auto mail = *mails.first();
141 QCOMPARE(mail.getSubject(), QString::fromLatin1("Re: Re: 1"));
142 });
143 VERIFYEXEC(job);
144 }
145
146 {
147 auto mail = Mail::create(mResourceInstanceIdentifier);
148 mail.setMimeMessage(message1->encodedContent());
149 mail.setFolder(folder);
150 VERIFYEXEC(Store::create(mail));
151 }
152
153 VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
154 {
155 auto job = Store::fetchAll<Mail>(query)
156 .syncThen<void, QList<Mail::Ptr>>([=](const QList<Mail::Ptr> &mails) {
157 QCOMPARE(mails.size(), 1);
158 auto mail = *mails.first();
159 QCOMPARE(mail.getSubject(), QString::fromLatin1("Re: Re: 1"));
160 });
161 VERIFYEXEC(job);
162 //TODO ensure we also find message 1 as part of thread.
163 }
164
165 /* VERIFYEXEC(Store::remove(mail)); */
166 /* VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); */
167 /* { */
168 /* auto job = Store::fetchAll<Mail>(Query::RequestedProperties(QByteArrayList() << Mail::Folder::name << Mail::Subject::name)) */
169 /* .syncThen<void, QList<Mail::Ptr>>([=](const QList<Mail::Ptr> &mails) { */
170 /* QCOMPARE(mails.size(), 0); */
171 /* }); */
172 /* VERIFYEXEC(job); */
173 /* } */
174 /* VERIFYEXEC(ResourceControl::flushReplayQueue(QByteArrayList() << mResourceInstanceIdentifier)); */
175}
176
94#include "mailthreadtest.moc" 177#include "mailthreadtest.moc"
diff --git a/tests/mailthreadtest.h b/tests/mailthreadtest.h
index d6b9c24..8730ec6 100644
--- a/tests/mailthreadtest.h
+++ b/tests/mailthreadtest.h
@@ -51,6 +51,7 @@ private slots:
51 void cleanup(); 51 void cleanup();
52 52
53 void testListThreadLeader(); 53 void testListThreadLeader();
54 void testIndexInMixedOrder();
54}; 55};
55 56
56} 57}