diff options
-rw-r--r-- | examples/imapresource/imapresource.cpp | 108 | ||||
-rw-r--r-- | examples/imapresource/imapresource.h | 3 | ||||
-rw-r--r-- | examples/imapresource/imapserverproxy.cpp | 76 | ||||
-rw-r--r-- | examples/imapresource/imapserverproxy.h | 22 | ||||
-rw-r--r-- | examples/imapresource/tests/imapresourcetest.cpp | 30 | ||||
-rw-r--r-- | examples/imapresource/tests/imapserverproxytest.cpp | 50 |
6 files changed, 232 insertions, 57 deletions
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp index 49cbb20..6adfeb3 100644 --- a/examples/imapresource/imapresource.cpp +++ b/examples/imapresource/imapresource.cpp | |||
@@ -59,6 +59,8 @@ ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPo | |||
59 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); | 59 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); |
60 | mServer = config.value("server").toString(); | 60 | mServer = config.value("server").toString(); |
61 | mPort = config.value("port").toInt(); | 61 | mPort = config.value("port").toInt(); |
62 | mUser = config.value("user").toString(); | ||
63 | mPassword = config.value("password").toString(); | ||
62 | 64 | ||
63 | // auto folderUpdater = new FolderUpdater(QByteArray()); | 65 | // auto folderUpdater = new FolderUpdater(QByteArray()); |
64 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, | 66 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, |
@@ -117,6 +119,15 @@ void ImapResource::synchronizeFolders(const QVector<Folder> &folderList, Sink::S | |||
117 | } | 119 | } |
118 | } | 120 | } |
119 | 121 | ||
122 | static QByteArray remoteIdForMessage(const QString &path, qint64 uid) | ||
123 | { | ||
124 | return path.toUtf8() + "/" + QByteArray::number(uid); | ||
125 | } | ||
126 | |||
127 | static qint64 uidFromMessageRemoteId(const QByteArray &remoteId) | ||
128 | { | ||
129 | return remoteId.split('/').last().toLongLong(); | ||
130 | } | ||
120 | void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages) | 131 | void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages) |
121 | { | 132 | { |
122 | auto time = QSharedPointer<QTime>::create(); | 133 | auto time = QSharedPointer<QTime>::create(); |
@@ -130,23 +141,6 @@ void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sin | |||
130 | 141 | ||
131 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | 142 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); |
132 | 143 | ||
133 | //This is not a full listing | ||
134 | // auto property = "folder"; | ||
135 | // scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
136 | // [&](const std::function<void(const QByteArray &)> &callback) { | ||
137 | // Index index(bufferType + ".index." + property, transaction); | ||
138 | // index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
139 | // callback(sinkId); | ||
140 | // }, | ||
141 | // [&](const Index::Error &error) { | ||
142 | // Warning() << "Error in index: " << error.message << property; | ||
143 | // }); | ||
144 | // }, | ||
145 | // [](const QByteArray &remoteId) -> bool { | ||
146 | // return QFile(remoteId).exists(); | ||
147 | // } | ||
148 | // ); | ||
149 | |||
150 | mSynchronizerQueue.startTransaction(); | 144 | mSynchronizerQueue.startTransaction(); |
151 | int count = 0; | 145 | int count = 0; |
152 | for (const auto &message : messages) { | 146 | for (const auto &message : messages) { |
@@ -170,8 +164,8 @@ void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sin | |||
170 | file.write(content); | 164 | file.write(content); |
171 | mail.setMimeMessagePath(filePath); | 165 | mail.setMimeMessagePath(filePath); |
172 | //FIXME Not sure if these are the actual flags | 166 | //FIXME Not sure if these are the actual flags |
173 | mail.setUnread(message.flags.contains("\\SEEN")); | 167 | mail.setUnread(!message.flags.contains(Imap::Flags::Seen)); |
174 | mail.setImportant(message.flags.contains("\\FLAGGED")); | 168 | mail.setImportant(message.flags.contains(Imap::Flags::Flagged)); |
175 | 169 | ||
176 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); | 170 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); |
177 | } | 171 | } |
@@ -180,11 +174,56 @@ void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sin | |||
180 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | 174 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; |
181 | } | 175 | } |
182 | 176 | ||
177 | void ImapResource::synchronizeRemovals(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QSet<qint64> &messages) | ||
178 | { | ||
179 | auto time = QSharedPointer<QTime>::create(); | ||
180 | time->start(); | ||
181 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
182 | |||
183 | Trace() << "Finding removed mail."; | ||
184 | |||
185 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | ||
186 | |||
187 | int count = 0; | ||
188 | auto property = Sink::ApplicationDomain::Mail::Folder::name; | ||
189 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
190 | [&](const std::function<void(const QByteArray &)> &callback) { | ||
191 | Index index(bufferType + ".index." + property, transaction); | ||
192 | index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
193 | callback(sinkId); | ||
194 | }, | ||
195 | [&](const Index::Error &error) { | ||
196 | Warning() << "Error in index: " << error.message << property; | ||
197 | }); | ||
198 | }, | ||
199 | [messages, path, &count](const QByteArray &remoteId) -> bool { | ||
200 | if (messages.contains(uidFromMessageRemoteId(remoteId))) { | ||
201 | return true; | ||
202 | } | ||
203 | count++; | ||
204 | return false; | ||
205 | } | ||
206 | ); | ||
207 | |||
208 | const auto elapsed = time->elapsed(); | ||
209 | Log() << "Removed " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
210 | } | ||
211 | |||
183 | KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | 212 | KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) |
184 | { | 213 | { |
185 | Log() << " Synchronizing"; | 214 | Log() << " Synchronizing"; |
186 | return KAsync::start<void>([this, &mainStore, &synchronizationStore](KAsync::Future<void> future) { | 215 | return KAsync::start<void>([this, &mainStore, &synchronizationStore](KAsync::Future<void> future) { |
187 | ImapServerProxy imap(mServer, mPort); | 216 | ImapServerProxy imap(mServer, mPort); |
217 | auto loginFuture = imap.login(mUser, mPassword).exec(); | ||
218 | loginFuture.waitForFinished(); | ||
219 | if (loginFuture.errorCode()) { | ||
220 | Warning() << "Login failed."; | ||
221 | future.setError(1, "Login failed"); | ||
222 | return; | ||
223 | } else { | ||
224 | Trace() << "Login was successful"; | ||
225 | } | ||
226 | |||
188 | QVector<Folder> folderList; | 227 | QVector<Folder> folderList; |
189 | auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QVector<Folder> &folders) { | 228 | auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QVector<Folder> &folders) { |
190 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | 229 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); |
@@ -197,8 +236,11 @@ KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, | |||
197 | }); | 236 | }); |
198 | folderFuture.waitForFinished(); | 237 | folderFuture.waitForFinished(); |
199 | if (folderFuture.errorCode()) { | 238 | if (folderFuture.errorCode()) { |
239 | Warning() << "Folder sync failed."; | ||
200 | future.setError(1, "Folder list sync failed"); | 240 | future.setError(1, "Folder list sync failed"); |
201 | return; | 241 | return; |
242 | } else { | ||
243 | Trace() << "Folder sync was successful"; | ||
202 | } | 244 | } |
203 | 245 | ||
204 | for (const auto &folder : folderList) { | 246 | for (const auto &folder : folderList) { |
@@ -220,30 +262,32 @@ KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, | |||
220 | // transaction.commit(); | 262 | // transaction.commit(); |
221 | // syncTransaction.commit(); | 263 | // syncTransaction.commit(); |
222 | 264 | ||
223 | auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder](const QVector<Message> &messages) { | 265 | QSet<qint64> uids; |
266 | auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder, &uids](const QVector<Message> &messages) { | ||
224 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | 267 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); |
225 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | 268 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); |
226 | Trace() << "Synchronizing mails" << folder.pathParts.join('/'); | 269 | Trace() << "Synchronizing mails" << folder.normalizedPath(); |
227 | synchronizeMails(transaction, syncTransaction, folder.pathParts.join('/'), messages); | 270 | for (const auto &msg : messages) { |
271 | uids << msg.uid; | ||
272 | } | ||
273 | synchronizeMails(transaction, syncTransaction, folder.normalizedPath(), messages); | ||
228 | transaction.commit(); | 274 | transaction.commit(); |
229 | syncTransaction.commit(); | 275 | syncTransaction.commit(); |
230 | }); | 276 | }); |
231 | messagesFuture.waitForFinished(); | 277 | messagesFuture.waitForFinished(); |
232 | if (messagesFuture.errorCode()) { | 278 | if (messagesFuture.errorCode()) { |
233 | future.setError(1, "Folder sync failed: " + folder.pathParts.join('/')); | 279 | future.setError(1, "Folder sync failed: " + folder.normalizedPath()); |
234 | return; | 280 | return; |
235 | } | 281 | } |
236 | Trace() << "Folder synchronized: " << folder.pathParts.join('/'); | 282 | //Remove what there is to remove |
283 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
284 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
285 | synchronizeRemovals(transaction, syncTransaction, folder.normalizedPath(), uids); | ||
286 | transaction.commit(); | ||
287 | syncTransaction.commit(); | ||
288 | Trace() << "Folder synchronized: " << folder.normalizedPath(); | ||
237 | } | 289 | } |
238 | 290 | ||
239 | |||
240 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadWrite); | ||
241 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
242 | // mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &data) { | ||
243 | // return true; | ||
244 | // }); | ||
245 | //TODO now fetch all folders and iterate over them and synchronize each one | ||
246 | |||
247 | Log() << "Done Synchronizing"; | 291 | Log() << "Done Synchronizing"; |
248 | future.setFinished(); | 292 | future.setFinished(); |
249 | }); | 293 | }); |
diff --git a/examples/imapresource/imapresource.h b/examples/imapresource/imapresource.h index 23b7e1a..82f96a4 100644 --- a/examples/imapresource/imapresource.h +++ b/examples/imapresource/imapresource.h | |||
@@ -52,12 +52,15 @@ private: | |||
52 | QByteArray createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); | 52 | QByteArray createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); |
53 | void synchronizeFolders(const QVector<Imap::Folder> &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); | 53 | void synchronizeFolders(const QVector<Imap::Folder> &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction); |
54 | void synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Imap::Message> &messages); | 54 | void synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Imap::Message> &messages); |
55 | void synchronizeRemovals(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QSet<qint64> &messages); | ||
55 | 56 | ||
56 | QSharedPointer<ImapMailAdaptorFactory> mMailAdaptorFactory; | 57 | QSharedPointer<ImapMailAdaptorFactory> mMailAdaptorFactory; |
57 | QSharedPointer<ImapFolderAdaptorFactory> mFolderAdaptorFactory; | 58 | QSharedPointer<ImapFolderAdaptorFactory> mFolderAdaptorFactory; |
58 | private: | 59 | private: |
59 | QString mServer; | 60 | QString mServer; |
60 | int mPort; | 61 | int mPort; |
62 | QString mUser; | ||
63 | QString mPassword; | ||
61 | }; | 64 | }; |
62 | 65 | ||
63 | class ImapResourceFactory : public Sink::ResourceFactory | 66 | class ImapResourceFactory : public Sink::ResourceFactory |
diff --git a/examples/imapresource/imapserverproxy.cpp b/examples/imapresource/imapserverproxy.cpp index 836a9bc..dfb134c 100644 --- a/examples/imapresource/imapserverproxy.cpp +++ b/examples/imapresource/imapserverproxy.cpp | |||
@@ -24,6 +24,9 @@ | |||
24 | #include <KIMAP/KIMAP/SelectJob> | 24 | #include <KIMAP/KIMAP/SelectJob> |
25 | #include <KIMAP/KIMAP/AppendJob> | 25 | #include <KIMAP/KIMAP/AppendJob> |
26 | #include <KIMAP/KIMAP/CreateJob> | 26 | #include <KIMAP/KIMAP/CreateJob> |
27 | #include <KIMAP/KIMAP/DeleteJob> | ||
28 | #include <KIMAP/KIMAP/StoreJob> | ||
29 | #include <KIMAP/KIMAP/ExpungeJob> | ||
27 | 30 | ||
28 | #include <KIMAP/KIMAP/SessionUiProxy> | 31 | #include <KIMAP/KIMAP/SessionUiProxy> |
29 | #include <KCoreAddons/KJob> | 32 | #include <KCoreAddons/KJob> |
@@ -32,10 +35,15 @@ | |||
32 | 35 | ||
33 | using namespace Imap; | 36 | using namespace Imap; |
34 | 37 | ||
38 | const char* Imap::Flags::Seen = "\\Seen"; | ||
39 | const char* Imap::Flags::Deleted = "\\Deleted"; | ||
40 | const char* Imap::Flags::Answered = "\\Answered"; | ||
41 | const char* Imap::Flags::Flagged = "\\Flagged"; | ||
42 | |||
35 | static KAsync::Job<void> runJob(KJob *job) | 43 | static KAsync::Job<void> runJob(KJob *job) |
36 | { | 44 | { |
37 | return KAsync::start<void>([job](KAsync::Future<void> &future) { | 45 | return KAsync::start<void>([job](KAsync::Future<void> &future) { |
38 | QObject::connect(job, &KJob::result, job, [&future](KJob *job) { | 46 | QObject::connect(job, &KJob::result, [&future](KJob *job) { |
39 | if (job->error()) { | 47 | if (job->error()) { |
40 | Warning() << "Job failed: " << job->errorString(); | 48 | Warning() << "Job failed: " << job->errorString(); |
41 | future.setError(job->error(), job->errorString()); | 49 | future.setError(job->error(), job->errorString()); |
@@ -62,9 +70,6 @@ ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port) : mSession( | |||
62 | 70 | ||
63 | KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString &password) | 71 | KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString &password) |
64 | { | 72 | { |
65 | if (mSession->state() == KIMAP::Session::State::Authenticated || mSession->state() == KIMAP::Session::State::Selected) { | ||
66 | return KAsync::null<void>(); | ||
67 | } | ||
68 | auto loginJob = new KIMAP::LoginJob(mSession); | 73 | auto loginJob = new KIMAP::LoginJob(mSession); |
69 | loginJob->setUserName(username); | 74 | loginJob->setUserName(username); |
70 | loginJob->setPassword(password); | 75 | loginJob->setPassword(password); |
@@ -75,9 +80,6 @@ KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString | |||
75 | 80 | ||
76 | KAsync::Job<void> ImapServerProxy::select(const QString &mailbox) | 81 | KAsync::Job<void> ImapServerProxy::select(const QString &mailbox) |
77 | { | 82 | { |
78 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
79 | return KAsync::error<void>(1, "Not connected"); | ||
80 | } | ||
81 | auto select = new KIMAP::SelectJob(mSession); | 83 | auto select = new KIMAP::SelectJob(mSession); |
82 | select->setMailBox(mailbox); | 84 | select->setMailBox(mailbox); |
83 | // select->setCondstoreEnabled(serverSupportsCondstore()); | 85 | // select->setCondstoreEnabled(serverSupportsCondstore()); |
@@ -86,9 +88,6 @@ KAsync::Job<void> ImapServerProxy::select(const QString &mailbox) | |||
86 | 88 | ||
87 | KAsync::Job<void> ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags, const QDateTime &internalDate) | 89 | KAsync::Job<void> ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags, const QDateTime &internalDate) |
88 | { | 90 | { |
89 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
90 | return KAsync::error<void>(1, "Not connected"); | ||
91 | } | ||
92 | auto append = new KIMAP::AppendJob(mSession); | 91 | auto append = new KIMAP::AppendJob(mSession); |
93 | append->setMailBox(mailbox); | 92 | append->setMailBox(mailbox); |
94 | append->setContent(content); | 93 | append->setContent(content); |
@@ -97,21 +96,38 @@ KAsync::Job<void> ImapServerProxy::append(const QString &mailbox, const QByteArr | |||
97 | return runJob(append); | 96 | return runJob(append); |
98 | } | 97 | } |
99 | 98 | ||
99 | KAsync::Job<void> ImapServerProxy::store(const KIMAP::ImapSet &set, const QList<QByteArray> &flags) | ||
100 | { | ||
101 | auto store = new KIMAP::StoreJob(mSession); | ||
102 | store->setUidBased(true); | ||
103 | store->setSequenceSet(set); | ||
104 | store->setFlags(flags); | ||
105 | store->setMode(KIMAP::StoreJob::AppendFlags); | ||
106 | return runJob(store); | ||
107 | } | ||
108 | |||
100 | KAsync::Job<void> ImapServerProxy::create(const QString &mailbox) | 109 | KAsync::Job<void> ImapServerProxy::create(const QString &mailbox) |
101 | { | 110 | { |
102 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
103 | return KAsync::error<void>(1, "Not connected"); | ||
104 | } | ||
105 | auto create = new KIMAP::CreateJob(mSession); | 111 | auto create = new KIMAP::CreateJob(mSession); |
106 | create->setMailBox(mailbox); | 112 | create->setMailBox(mailbox); |
107 | return runJob(create); | 113 | return runJob(create); |
108 | } | 114 | } |
109 | 115 | ||
116 | KAsync::Job<void> ImapServerProxy::remove(const QString &mailbox) | ||
117 | { | ||
118 | auto job = new KIMAP::DeleteJob(mSession); | ||
119 | job->setMailBox(mailbox); | ||
120 | return runJob(job); | ||
121 | } | ||
122 | |||
123 | KAsync::Job<void> ImapServerProxy::expunge() | ||
124 | { | ||
125 | auto job = new KIMAP::ExpungeJob(mSession); | ||
126 | return runJob(job); | ||
127 | } | ||
128 | |||
110 | KAsync::Job<void> ImapServerProxy::fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback) | 129 | KAsync::Job<void> ImapServerProxy::fetch(const KIMAP::ImapSet &set, KIMAP::FetchJob::FetchScope scope, FetchCallback callback) |
111 | { | 130 | { |
112 | if (mSession->state() == KIMAP::Session::State::Disconnected) { | ||
113 | return KAsync::error<void>(1, "Not connected"); | ||
114 | } | ||
115 | auto fetch = new KIMAP::FetchJob(mSession); | 131 | auto fetch = new KIMAP::FetchJob(mSession); |
116 | fetch->setSequenceSet(set); | 132 | fetch->setSequenceSet(set); |
117 | fetch->setUidBased(true); | 133 | fetch->setUidBased(true); |
@@ -164,34 +180,48 @@ KAsync::Job<void> ImapServerProxy::list(KIMAP::ListJob::Option option, const std | |||
164 | // listJob->setQueriedNamespaces(serverNamespaces()); | 180 | // listJob->setQueriedNamespaces(serverNamespaces()); |
165 | QObject::connect(listJob, &KIMAP::ListJob::mailBoxesReceived, | 181 | QObject::connect(listJob, &KIMAP::ListJob::mailBoxesReceived, |
166 | listJob, callback); | 182 | listJob, callback); |
183 | //Figure out the separator character on the first list issued. | ||
184 | if (mSeparatorCharacter.isNull()) { | ||
185 | QObject::connect(listJob, &KIMAP::ListJob::mailBoxesReceived, | ||
186 | listJob, [this](const QList<KIMAP::MailBoxDescriptor> &mailboxes,const QList<QList<QByteArray> > &flags) { | ||
187 | if (!mailboxes.isEmpty() && mSeparatorCharacter.isNull()) { | ||
188 | mSeparatorCharacter = mailboxes.first().separator; | ||
189 | } | ||
190 | } | ||
191 | ); | ||
192 | } | ||
167 | return runJob(listJob); | 193 | return runJob(listJob); |
168 | } | 194 | } |
169 | 195 | ||
196 | KAsync::Job<void> ImapServerProxy::remove(const QString &mailbox, const QByteArray &imapSet) | ||
197 | { | ||
198 | const auto set = KIMAP::ImapSet::fromImapSequenceSet(imapSet); | ||
199 | return select(mailbox).then<void>(store(set, QByteArrayList() << Flags::Deleted)).then<void>(expunge()); | ||
200 | } | ||
201 | |||
170 | KAsync::Future<void> ImapServerProxy::fetchFolders(std::function<void(const QVector<Folder> &)> callback) | 202 | KAsync::Future<void> ImapServerProxy::fetchFolders(std::function<void(const QVector<Folder> &)> callback) |
171 | { | 203 | { |
172 | Trace() << "Fetching folders"; | 204 | Trace() << "Fetching folders"; |
173 | auto job = login("doe", "doe").then<void>(list(KIMAP::ListJob::IncludeUnsubscribed, [callback](const QList<KIMAP::MailBoxDescriptor> &mailboxes, const QList<QList<QByteArray> > &flags){ | 205 | auto job = list(KIMAP::ListJob::IncludeUnsubscribed, [callback](const QList<KIMAP::MailBoxDescriptor> &mailboxes, const QList<QList<QByteArray> > &flags){ |
174 | QVector<Folder> list; | 206 | QVector<Folder> list; |
175 | for (const auto &mailbox : mailboxes) { | 207 | for (const auto &mailbox : mailboxes) { |
176 | Trace() << "Found mailbox: " << mailbox.name; | 208 | Trace() << "Found mailbox: " << mailbox.name; |
177 | list << Folder{mailbox.name.split(mailbox.separator)}; | 209 | list << Folder{mailbox.name.split(mailbox.separator)}; |
178 | } | 210 | } |
179 | callback(list); | 211 | callback(list); |
180 | }), | ||
181 | [](int errorCode, const QString &errorString) { | ||
182 | Warning() << "Failed to list folders: " << errorCode << errorString; | ||
183 | }); | 212 | }); |
184 | return job.exec(); | 213 | return job.exec(); |
185 | } | 214 | } |
186 | 215 | ||
187 | KAsync::Future<void> ImapServerProxy::fetchMessages(const Folder &folder, std::function<void(const QVector<Message> &)> callback) | 216 | KAsync::Future<void> ImapServerProxy::fetchMessages(const Folder &folder, std::function<void(const QVector<Message> &)> callback) |
188 | { | 217 | { |
189 | //TODO use the right separator | 218 | Q_ASSERT(!mSeparatorCharacter.isNull()); |
190 | auto job = login("doe", "doe").then<void>(select(folder.pathParts.join('.'))).then<void, KAsync::Job<void>>([this, callback, folder]() -> KAsync::Job<void> { | 219 | auto job = select(folder.pathParts.join(mSeparatorCharacter)).then<void, KAsync::Job<void>>([this, callback, folder]() -> KAsync::Job<void> { |
191 | return fetchHeaders(folder.pathParts.join('.')).then<void, KAsync::Job<void>, QList<qint64>>([this, callback](const QList<qint64> &uidsToFetch){ | 220 | return fetchHeaders(folder.pathParts.join(mSeparatorCharacter)).then<void, KAsync::Job<void>, QList<qint64>>([this, callback](const QList<qint64> &uidsToFetch){ |
192 | Trace() << "Uids to fetch: " << uidsToFetch; | 221 | Trace() << "Uids to fetch: " << uidsToFetch; |
193 | if (uidsToFetch.isEmpty()) { | 222 | if (uidsToFetch.isEmpty()) { |
194 | Trace() << "Nothing to fetch"; | 223 | Trace() << "Nothing to fetch"; |
224 | callback(QVector<Message>()); | ||
195 | return KAsync::null<void>(); | 225 | return KAsync::null<void>(); |
196 | } | 226 | } |
197 | KIMAP::FetchJob::FetchScope scope; | 227 | KIMAP::FetchJob::FetchScope scope; |
diff --git a/examples/imapresource/imapserverproxy.h b/examples/imapresource/imapserverproxy.h index 475a45d..7000c67 100644 --- a/examples/imapresource/imapserverproxy.h +++ b/examples/imapresource/imapserverproxy.h | |||
@@ -28,6 +28,18 @@ | |||
28 | 28 | ||
29 | namespace Imap { | 29 | namespace Imap { |
30 | 30 | ||
31 | namespace Flags | ||
32 | { | ||
33 | /// The flag for a message being seen (i.e. opened by user). | ||
34 | extern const char* Seen; | ||
35 | /// The flag for a message being deleted by the user. | ||
36 | extern const char* Deleted; | ||
37 | /// The flag for a message being replied to by the user. | ||
38 | extern const char* Answered; | ||
39 | /// The flag for a message being marked as flagged. | ||
40 | extern const char* Flagged; | ||
41 | } | ||
42 | |||
31 | struct Message { | 43 | struct Message { |
32 | qint64 uid; | 44 | qint64 uid; |
33 | qint64 size; | 45 | qint64 size; |
@@ -37,11 +49,17 @@ struct Message { | |||
37 | }; | 49 | }; |
38 | 50 | ||
39 | struct Folder { | 51 | struct Folder { |
52 | QString normalizedPath() const | ||
53 | { | ||
54 | return pathParts.join('/'); | ||
55 | } | ||
56 | |||
40 | QList<QString> pathParts; | 57 | QList<QString> pathParts; |
41 | }; | 58 | }; |
42 | 59 | ||
43 | class ImapServerProxy { | 60 | class ImapServerProxy { |
44 | KIMAP::Session *mSession; | 61 | KIMAP::Session *mSession; |
62 | QChar mSeparatorCharacter; | ||
45 | public: | 63 | public: |
46 | ImapServerProxy(const QString &serverUrl, int port); | 64 | ImapServerProxy(const QString &serverUrl, int port); |
47 | 65 | ||
@@ -49,7 +67,10 @@ public: | |||
49 | KAsync::Job<void> login(const QString &username, const QString &password); | 67 | KAsync::Job<void> login(const QString &username, const QString &password); |
50 | KAsync::Job<void> select(const QString &mailbox); | 68 | KAsync::Job<void> select(const QString &mailbox); |
51 | KAsync::Job<void> append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags = QList<QByteArray>(), const QDateTime &internalDate = QDateTime()); | 69 | KAsync::Job<void> append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags = QList<QByteArray>(), const QDateTime &internalDate = QDateTime()); |
70 | KAsync::Job<void> store(const KIMAP::ImapSet &set, const QList<QByteArray> &flags); | ||
52 | KAsync::Job<void> create(const QString &mailbox); | 71 | KAsync::Job<void> create(const QString &mailbox); |
72 | KAsync::Job<void> remove(const QString &mailbox); | ||
73 | KAsync::Job<void> expunge(); | ||
53 | 74 | ||
54 | typedef std::function<void(const QString &, | 75 | typedef std::function<void(const QString &, |
55 | const QMap<qint64,qint64> &, | 76 | const QMap<qint64,qint64> &, |
@@ -63,6 +84,7 @@ public: | |||
63 | 84 | ||
64 | //Composed calls that do login etc. | 85 | //Composed calls that do login etc. |
65 | KAsync::Job<QList<qint64>> fetchHeaders(const QString &mailbox); | 86 | KAsync::Job<QList<qint64>> fetchHeaders(const QString &mailbox); |
87 | KAsync::Job<void> remove(const QString &mailbox, const QByteArray &imapSet); | ||
66 | 88 | ||
67 | KAsync::Future<void> fetchFolders(std::function<void(const QVector<Folder> &)> callback); | 89 | KAsync::Future<void> fetchFolders(std::function<void(const QVector<Folder> &)> callback); |
68 | KAsync::Future<void> fetchMessages(const Folder &folder, std::function<void(const QVector<Message> &)> callback); | 90 | KAsync::Future<void> fetchMessages(const Folder &folder, std::function<void(const QVector<Message> &)> callback); |
diff --git a/examples/imapresource/tests/imapresourcetest.cpp b/examples/imapresource/tests/imapresourcetest.cpp index 8ce32c2..30bb953 100644 --- a/examples/imapresource/tests/imapresourcetest.cpp +++ b/examples/imapresource/tests/imapresourcetest.cpp | |||
@@ -65,6 +65,8 @@ private slots: | |||
65 | resource.setProperty("identifier", "org.kde.imap.instance1"); | 65 | resource.setProperty("identifier", "org.kde.imap.instance1"); |
66 | resource.setProperty("type", "org.kde.imap"); | 66 | resource.setProperty("type", "org.kde.imap"); |
67 | resource.setProperty("server", "localhost"); | 67 | resource.setProperty("server", "localhost"); |
68 | resource.setProperty("user", "doe"); | ||
69 | resource.setProperty("password", "doe"); | ||
68 | resource.setProperty("port", 993); | 70 | resource.setProperty("port", 993); |
69 | Sink::Store::create(resource).exec().waitForFinished(); | 71 | Sink::Store::create(resource).exec().waitForFinished(); |
70 | } | 72 | } |
@@ -164,8 +166,8 @@ private slots: | |||
164 | VERIFYEXEC(Store::synchronize(query)); | 166 | VERIFYEXEC(Store::synchronize(query)); |
165 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | 167 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); |
166 | 168 | ||
167 | ImapServerProxy imap("localhost", 993); | 169 | Imap::ImapServerProxy imap("localhost", 993); |
168 | imap.login("doe", "doe").exec().waitForFinished(); | 170 | VERIFYEXEC(imap.login("doe", "doe")); |
169 | 171 | ||
170 | auto msg = KMime::Message::Ptr::create(); | 172 | auto msg = KMime::Message::Ptr::create(); |
171 | msg->subject(true)->fromUnicodeString("Foobar", "utf8"); | 173 | msg->subject(true)->fromUnicodeString("Foobar", "utf8"); |
@@ -182,6 +184,30 @@ private slots: | |||
182 | VERIFYEXEC(job); | 184 | VERIFYEXEC(job); |
183 | } | 185 | } |
184 | 186 | ||
187 | void testFetchRemovedMessages() | ||
188 | { | ||
189 | Sink::Query query; | ||
190 | query.resources << "org.kde.imap.instance1"; | ||
191 | query.request<Mail::Subject>().request<Mail::MimeMessage>(); | ||
192 | |||
193 | // Ensure all local data is processed | ||
194 | VERIFYEXEC(Store::synchronize(query)); | ||
195 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
196 | |||
197 | Imap::ImapServerProxy imap("localhost", 993); | ||
198 | VERIFYEXEC(imap.login("doe", "doe")); | ||
199 | |||
200 | VERIFYEXEC(imap.remove("INBOX.test", "2:*")); | ||
201 | |||
202 | Store::synchronize(query).exec().waitForFinished(); | ||
203 | ResourceControl::flushMessageQueue(query.resources).exec().waitForFinished(); | ||
204 | |||
205 | auto job = Store::fetchAll<Mail>(query).then<void, QList<Mail::Ptr>>([](const QList<Mail::Ptr> &mails) { | ||
206 | QCOMPARE(mails.size(), 1); | ||
207 | }); | ||
208 | VERIFYEXEC(job); | ||
209 | } | ||
210 | |||
185 | void testFailingSync() | 211 | void testFailingSync() |
186 | { | 212 | { |
187 | auto resource = ApplicationDomain::ImapResource::create("account1"); | 213 | auto resource = ApplicationDomain::ImapResource::create("account1"); |
diff --git a/examples/imapresource/tests/imapserverproxytest.cpp b/examples/imapresource/tests/imapserverproxytest.cpp index 139597a..b30cc33 100644 --- a/examples/imapresource/tests/imapserverproxytest.cpp +++ b/examples/imapresource/tests/imapserverproxytest.cpp | |||
@@ -79,6 +79,7 @@ private slots: | |||
79 | void testFetchFolders() | 79 | void testFetchFolders() |
80 | { | 80 | { |
81 | ImapServerProxy imap("localhost", 993); | 81 | ImapServerProxy imap("localhost", 993); |
82 | VERIFYEXEC(imap.login("doe", "doe")); | ||
82 | auto future = imap.fetchFolders([](const QVector<Folder> &){}); | 83 | auto future = imap.fetchFolders([](const QVector<Folder> &){}); |
83 | future.waitForFinished(); | 84 | future.waitForFinished(); |
84 | QVERIFY(!future.errorCode()); | 85 | QVERIFY(!future.errorCode()); |
@@ -93,6 +94,55 @@ private slots: | |||
93 | QVERIFY(future2.errorCode()); | 94 | QVERIFY(future2.errorCode()); |
94 | } | 95 | } |
95 | 96 | ||
97 | void testFetchMail() | ||
98 | { | ||
99 | ImapServerProxy imap("localhost", 993); | ||
100 | VERIFYEXEC(imap.login("doe", "doe")); | ||
101 | |||
102 | KIMAP::FetchJob::FetchScope scope; | ||
103 | scope.mode = KIMAP::FetchJob::FetchScope::Headers; | ||
104 | int count = 0; | ||
105 | auto job = imap.select("INBOX.test").then<void>(imap.fetch(KIMAP::ImapSet::fromImapSequenceSet("1:*"), scope, | ||
106 | [&count](const QString &mailbox, | ||
107 | const QMap<qint64,qint64> &uids, | ||
108 | const QMap<qint64,qint64> &sizes, | ||
109 | const QMap<qint64,KIMAP::MessageAttribute> &attrs, | ||
110 | const QMap<qint64,KIMAP::MessageFlags> &flags, | ||
111 | const QMap<qint64,KIMAP::MessagePtr> &messages) { | ||
112 | Trace() << "Received " << uids.size() << " messages from " << mailbox; | ||
113 | Trace() << uids.size() << sizes.size() << attrs.size() << flags.size() << messages.size(); | ||
114 | count += uids.size(); | ||
115 | })); | ||
116 | |||
117 | VERIFYEXEC(job); | ||
118 | QCOMPARE(count, 1); | ||
119 | } | ||
120 | |||
121 | void testRemoveMail() | ||
122 | { | ||
123 | ImapServerProxy imap("localhost", 993); | ||
124 | VERIFYEXEC(imap.login("doe", "doe")); | ||
125 | VERIFYEXEC(imap.remove("INBOX.test", "1:*")); | ||
126 | |||
127 | KIMAP::FetchJob::FetchScope scope; | ||
128 | scope.mode = KIMAP::FetchJob::FetchScope::Headers; | ||
129 | int count = 0; | ||
130 | auto job = imap.select("INBOX.test").then<void>(imap.fetch(KIMAP::ImapSet::fromImapSequenceSet("1:*"), scope, | ||
131 | [&count](const QString &mailbox, | ||
132 | const QMap<qint64,qint64> &uids, | ||
133 | const QMap<qint64,qint64> &sizes, | ||
134 | const QMap<qint64,KIMAP::MessageAttribute> &attrs, | ||
135 | const QMap<qint64,KIMAP::MessageFlags> &flags, | ||
136 | const QMap<qint64,KIMAP::MessagePtr> &messages) { | ||
137 | Trace() << "Received " << uids.size() << " messages from " << mailbox; | ||
138 | Trace() << uids.size() << sizes.size() << attrs.size() << flags.size() << messages.size(); | ||
139 | count += uids.size(); | ||
140 | })); | ||
141 | |||
142 | VERIFYEXEC(job); | ||
143 | QCOMPARE(count, 0); | ||
144 | } | ||
145 | |||
96 | }; | 146 | }; |
97 | 147 | ||
98 | QTEST_MAIN(ImapServerProxyTest) | 148 | QTEST_MAIN(ImapServerProxyTest) |