diff options
Diffstat (limited to 'examples/imapresource/imapresource.cpp')
-rw-r--r-- | examples/imapresource/imapresource.cpp | 558 |
1 files changed, 341 insertions, 217 deletions
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp index 61031d8..72cc058 100644 --- a/examples/imapresource/imapresource.cpp +++ b/examples/imapresource/imapresource.cpp | |||
@@ -35,6 +35,10 @@ | |||
35 | #include "facadefactory.h" | 35 | #include "facadefactory.h" |
36 | #include "indexupdater.h" | 36 | #include "indexupdater.h" |
37 | #include "inspection.h" | 37 | #include "inspection.h" |
38 | #include "synchronizer.h" | ||
39 | #include "sourcewriteback.h" | ||
40 | #include "entitystore.h" | ||
41 | #include "remoteidmap.h" | ||
38 | #include <QDate> | 42 | #include <QDate> |
39 | #include <QUuid> | 43 | #include <QUuid> |
40 | #include <QDir> | 44 | #include <QDir> |
@@ -51,14 +55,26 @@ | |||
51 | 55 | ||
52 | using namespace Imap; | 56 | using namespace Imap; |
53 | 57 | ||
54 | class FolderUpdater : public Sink::Preprocessor | 58 | class MailPropertyExtractor : public Sink::Preprocessor |
55 | { | 59 | { |
56 | public: | 60 | public: |
57 | FolderUpdater(const QByteArray &drafts) {} | 61 | MailPropertyExtractor() {} |
58 | 62 | ||
59 | void updatedIndexedProperties(Sink::ApplicationDomain::BufferAdaptor &newEntity) | 63 | void updatedIndexedProperties(Sink::ApplicationDomain::BufferAdaptor &newEntity) |
60 | { | 64 | { |
61 | const auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); | 65 | const auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); |
66 | // auto parts = mimeMessagePath.split('/'); | ||
67 | // const auto key = parts.takeLast(); | ||
68 | // const auto path = parts.join("/") + "/cur/"; | ||
69 | // | ||
70 | // QDir dir(path); | ||
71 | // const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); | ||
72 | // if (list.size() != 1) { | ||
73 | // Warning() << "Failed to find message " << path << key << list.size(); | ||
74 | // return; | ||
75 | // } | ||
76 | |||
77 | // QString file; | ||
62 | QFile f(mimeMessagePath); | 78 | QFile f(mimeMessagePath); |
63 | if (!f.open(QIODevice::ReadOnly)) { | 79 | if (!f.open(QIODevice::ReadOnly)) { |
64 | Warning() << "Failed to open the file: " << mimeMessagePath; | 80 | Warning() << "Failed to open the file: " << mimeMessagePath; |
@@ -75,6 +91,7 @@ public: | |||
75 | msg->parse(); | 91 | msg->parse(); |
76 | 92 | ||
77 | newEntity.setProperty("subject", msg->subject(true)->asUnicodeString()); | 93 | newEntity.setProperty("subject", msg->subject(true)->asUnicodeString()); |
94 | newEntity.setProperty("subject", msg->subject(true)->asUnicodeString()); | ||
78 | newEntity.setProperty("sender", msg->from(true)->asUnicodeString()); | 95 | newEntity.setProperty("sender", msg->from(true)->asUnicodeString()); |
79 | newEntity.setProperty("senderName", msg->from(true)->asUnicodeString()); | 96 | newEntity.setProperty("senderName", msg->from(true)->asUnicodeString()); |
80 | newEntity.setProperty("date", msg->date(true)->dateTime()); | 97 | newEntity.setProperty("date", msg->date(true)->dateTime()); |
@@ -95,257 +112,268 @@ public: | |||
95 | { | 112 | { |
96 | } | 113 | } |
97 | 114 | ||
98 | QSharedPointer<ImapFolderAdaptorFactory> mFolderAdaptorFactory; | ||
99 | }; | 115 | }; |
100 | 116 | ||
101 | ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | 117 | class ImapSynchronizer : public Sink::Synchronizer { |
102 | : Sink::GenericResource(instanceIdentifier, pipeline), | 118 | public: |
103 | mMailAdaptorFactory(QSharedPointer<ImapMailAdaptorFactory>::create()), | 119 | ImapSynchronizer(const QByteArray &resourceType, const QByteArray &resourceInstanceIdentifier) |
104 | mFolderAdaptorFactory(QSharedPointer<ImapFolderAdaptorFactory>::create()) | 120 | : Sink::Synchronizer(resourceType, resourceInstanceIdentifier) |
105 | { | 121 | { |
106 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); | ||
107 | mServer = config.value("server").toString(); | ||
108 | mPort = config.value("port").toInt(); | ||
109 | mUser = config.value("user").toString(); | ||
110 | mPassword = config.value("password").toString(); | ||
111 | |||
112 | auto folderUpdater = new FolderUpdater(QByteArray()); | ||
113 | folderUpdater->mFolderAdaptorFactory = mFolderAdaptorFactory; | ||
114 | 122 | ||
115 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, | 123 | } |
116 | QVector<Sink::Preprocessor*>() << folderUpdater << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
117 | addType(ENTITY_TYPE_FOLDER, mFolderAdaptorFactory, | ||
118 | QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
119 | } | ||
120 | 124 | ||
121 | QByteArray ImapResource::createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | 125 | QByteArray createFolder(const QString &folderPath, const QByteArray &icon) |
122 | { | 126 | { |
123 | auto remoteId = folderPath.toUtf8(); | 127 | auto remoteId = folderPath.toUtf8(); |
124 | auto bufferType = ENTITY_TYPE_FOLDER; | 128 | auto bufferType = ENTITY_TYPE_FOLDER; |
125 | Sink::ApplicationDomain::Folder folder; | 129 | Sink::ApplicationDomain::Folder folder; |
126 | auto folderPathParts = folderPath.split('/'); | 130 | auto folderPathParts = folderPath.split('/'); |
127 | const auto name = folderPathParts.takeLast(); | 131 | const auto name = folderPathParts.takeLast(); |
128 | folder.setProperty("name", name); | 132 | folder.setProperty("name", name); |
129 | folder.setProperty("icon", icon); | 133 | folder.setProperty("icon", icon); |
130 | 134 | ||
131 | if (!folderPathParts.isEmpty()) { | 135 | if (!folderPathParts.isEmpty()) { |
132 | folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, folderPathParts.join('/').toUtf8(), synchronizationTransaction)); | 136 | folder.setProperty("parent", syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, folderPathParts.join('/').toUtf8())); |
137 | } | ||
138 | createOrModify(bufferType, remoteId, folder); | ||
139 | return remoteId; | ||
133 | } | 140 | } |
134 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); | ||
135 | return remoteId; | ||
136 | } | ||
137 | 141 | ||
138 | void ImapResource::synchronizeFolders(const QVector<Folder> &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | 142 | void synchronizeFolders(const QVector<Folder> &folderList) |
139 | { | 143 | { |
140 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | 144 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; |
141 | Trace() << "Found folders " << folderList.size(); | 145 | Trace() << "Found folders " << folderList.size(); |
142 | 146 | ||
143 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | 147 | scanForRemovals(bufferType, |
144 | [&bufferType, &transaction](const std::function<void(const QByteArray &)> &callback) { | 148 | [this, &bufferType](const std::function<void(const QByteArray &)> &callback) { |
145 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | 149 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, |
146 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | 150 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, |
147 | //but we currently fail to iterate over all entries in an index it seems. | 151 | //but we currently fail to iterate over all entries in an index it seems. |
148 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); | 152 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); |
149 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, bufferType); | 153 | auto mainDatabase = Sink::Storage::mainDatabase(transaction(), bufferType); |
150 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | 154 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { |
151 | callback(key); | 155 | callback(key); |
152 | return true; | ||
153 | }); | ||
154 | }, | ||
155 | [&folderList](const QByteArray &remoteId) -> bool { | ||
156 | //folderList.contains(remoteId) | ||
157 | for (const auto folderPath : folderList) { | ||
158 | if (folderPath.pathParts.join('/') == remoteId) { | ||
159 | return true; | 156 | return true; |
157 | }); | ||
158 | }, | ||
159 | [&folderList](const QByteArray &remoteId) -> bool { | ||
160 | //folderList.contains(remoteId) | ||
161 | for (const auto folderPath : folderList) { | ||
162 | if (folderPath.pathParts.join('/') == remoteId) { | ||
163 | return true; | ||
164 | } | ||
160 | } | 165 | } |
166 | return false; | ||
161 | } | 167 | } |
162 | return false; | 168 | ); |
169 | |||
170 | for (const auto folderPath : folderList) { | ||
171 | createFolder(folderPath.pathParts.join('/'), "folder"); | ||
163 | } | 172 | } |
164 | ); | 173 | } |
165 | 174 | ||
166 | for (const auto folderPath : folderList) { | 175 | static QByteArray remoteIdForMessage(const QString &path, qint64 uid) |
167 | createFolder(folderPath.pathParts.join('/'), "folder", transaction, synchronizationTransaction); | 176 | { |
177 | return path.toUtf8() + "/" + QByteArray::number(uid); | ||
168 | } | 178 | } |
169 | } | ||
170 | 179 | ||
171 | static QByteArray remoteIdForMessage(const QString &path, qint64 uid) | 180 | static qint64 uidFromMessageRemoteId(const QByteArray &remoteId) |
172 | { | 181 | { |
173 | return path.toUtf8() + "/" + QByteArray::number(uid); | 182 | return remoteId.split('/').last().toLongLong(); |
174 | } | 183 | } |
175 | 184 | ||
176 | static qint64 uidFromMessageRemoteId(const QByteArray &remoteId) | 185 | void synchronizeMails(const QString &path, const QVector<Message> &messages) |
177 | { | 186 | { |
178 | return remoteId.split('/').last().toLongLong(); | 187 | auto time = QSharedPointer<QTime>::create(); |
179 | } | 188 | time->start(); |
180 | void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages) | 189 | const QByteArray bufferType = ENTITY_TYPE_MAIL; |
181 | { | ||
182 | auto time = QSharedPointer<QTime>::create(); | ||
183 | time->start(); | ||
184 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
185 | 190 | ||
186 | 191 | ||
187 | Trace() << "Importing new mail."; | 192 | Trace() << "Importing new mail."; |
188 | 193 | ||
189 | // Trace() << "Looking into " << listingPath; | 194 | // Trace() << "Looking into " << listingPath; |
190 | 195 | ||
191 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | 196 | const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8()); |
192 | 197 | ||
193 | mSynchronizerQueue.startTransaction(); | 198 | int count = 0; |
194 | int count = 0; | 199 | for (const auto &message : messages) { |
195 | for (const auto &message : messages) { | 200 | count++; |
196 | count++; | 201 | const auto remoteId = path.toUtf8() + "/" + QByteArray::number(message.uid); |
197 | const auto remoteId = path.toUtf8() + "/" + QByteArray::number(message.uid); | ||
198 | 202 | ||
199 | Trace() << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; | 203 | Trace() << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; |
200 | 204 | ||
201 | Sink::ApplicationDomain::Mail mail; | 205 | Sink::ApplicationDomain::Mail mail; |
202 | mail.setFolder(folderLocalId); | 206 | mail.setFolder(folderLocalId); |
203 | //FIXME this should come from the mime message, extracted in the pipeline | 207 | //FIXME this should come from the mime message, extracted in the pipeline |
204 | mail.setExtractedSubject(message.msg->subject(true)->asUnicodeString()); | 208 | mail.setExtractedSubject(message.msg->subject(true)->asUnicodeString()); |
205 | 209 | ||
206 | auto filePath = Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + remoteId; | 210 | auto filePath = Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + remoteId; |
207 | QDir().mkpath(Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + path.toUtf8()); | 211 | QDir().mkpath(Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + path.toUtf8()); |
208 | QFile file(filePath); | 212 | QFile file(filePath); |
209 | if (!file.open(QIODevice::WriteOnly)) { | 213 | if (!file.open(QIODevice::WriteOnly)) { |
210 | Warning() << "Failed to open file for writing: " << file.errorString(); | 214 | Warning() << "Failed to open file for writing: " << file.errorString(); |
215 | } | ||
216 | const auto content = message.msg->encodedContent(); | ||
217 | file.write(content); | ||
218 | mail.setMimeMessagePath(filePath); | ||
219 | //FIXME Not sure if these are the actual flags | ||
220 | mail.setUnread(!message.flags.contains(Imap::Flags::Seen)); | ||
221 | mail.setImportant(message.flags.contains(Imap::Flags::Flagged)); | ||
222 | |||
223 | createOrModify(bufferType, remoteId, mail); | ||
211 | } | 224 | } |
212 | const auto content = message.msg->encodedContent(); | 225 | commitSync(); |
213 | file.write(content); | 226 | const auto elapsed = time->elapsed(); |
214 | mail.setMimeMessagePath(filePath); | 227 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; |
215 | //FIXME Not sure if these are the actual flags | ||
216 | mail.setUnread(!message.flags.contains(Imap::Flags::Seen)); | ||
217 | mail.setImportant(message.flags.contains(Imap::Flags::Flagged)); | ||
218 | |||
219 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); | ||
220 | } | 228 | } |
221 | mSynchronizerQueue.commit(); | ||
222 | const auto elapsed = time->elapsed(); | ||
223 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
224 | } | ||
225 | |||
226 | void ImapResource::synchronizeRemovals(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QSet<qint64> &messages) | ||
227 | { | ||
228 | auto time = QSharedPointer<QTime>::create(); | ||
229 | time->start(); | ||
230 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
231 | |||
232 | Trace() << "Finding removed mail."; | ||
233 | |||
234 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | ||
235 | 229 | ||
236 | int count = 0; | 230 | void synchronizeRemovals(const QString &path, const QSet<qint64> &messages) |
237 | auto property = Sink::ApplicationDomain::Mail::Folder::name; | 231 | { |
238 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | 232 | auto time = QSharedPointer<QTime>::create(); |
239 | [&](const std::function<void(const QByteArray &)> &callback) { | 233 | time->start(); |
240 | Index index(bufferType + ".index." + property, transaction); | 234 | const QByteArray bufferType = ENTITY_TYPE_MAIL; |
241 | index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | 235 | |
242 | callback(sinkId); | 236 | Trace() << "Finding removed mail."; |
237 | |||
238 | const auto folderLocalId = syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8()); | ||
239 | |||
240 | int count = 0; | ||
241 | auto property = Sink::ApplicationDomain::Mail::Folder::name; | ||
242 | scanForRemovals(bufferType, | ||
243 | [&](const std::function<void(const QByteArray &)> &callback) { | ||
244 | Index index(bufferType + ".index." + property, transaction()); | ||
245 | index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
246 | callback(sinkId); | ||
247 | }, | ||
248 | [&](const Index::Error &error) { | ||
249 | Warning() << "Error in index: " << error.message << property; | ||
250 | }); | ||
243 | }, | 251 | }, |
244 | [&](const Index::Error &error) { | 252 | [messages, path, &count](const QByteArray &remoteId) -> bool { |
245 | Warning() << "Error in index: " << error.message << property; | 253 | if (messages.contains(uidFromMessageRemoteId(remoteId))) { |
246 | }); | 254 | return true; |
247 | }, | 255 | } |
248 | [messages, path, &count](const QByteArray &remoteId) -> bool { | 256 | count++; |
249 | if (messages.contains(uidFromMessageRemoteId(remoteId))) { | 257 | return false; |
250 | return true; | ||
251 | } | 258 | } |
252 | count++; | 259 | ); |
253 | return false; | ||
254 | } | ||
255 | ); | ||
256 | |||
257 | const auto elapsed = time->elapsed(); | ||
258 | Log() << "Removed " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
259 | } | ||
260 | 260 | ||
261 | KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | 261 | const auto elapsed = time->elapsed(); |
262 | { | 262 | Log() << "Removed " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; |
263 | Log() << " Synchronizing"; | 263 | } |
264 | return KAsync::start<void>([this, &mainStore, &synchronizationStore](KAsync::Future<void> future) { | ||
265 | ImapServerProxy imap(mServer, mPort); | ||
266 | auto loginFuture = imap.login(mUser, mPassword).exec(); | ||
267 | loginFuture.waitForFinished(); | ||
268 | if (loginFuture.errorCode()) { | ||
269 | Warning() << "Login failed."; | ||
270 | future.setError(1, "Login failed"); | ||
271 | return; | ||
272 | } else { | ||
273 | Trace() << "Login was successful"; | ||
274 | } | ||
275 | 264 | ||
276 | QVector<Folder> folderList; | 265 | KAsync::Job<void> synchronizeWithSource() Q_DECL_OVERRIDE |
277 | auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QVector<Folder> &folders) { | 266 | { |
278 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | 267 | Log() << " Synchronizing"; |
279 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | 268 | return KAsync::start<void>([this](KAsync::Future<void> future) { |
280 | synchronizeFolders(folders, transaction, syncTransaction); | 269 | ImapServerProxy imap(mServer, mPort); |
281 | transaction.commit(); | 270 | auto loginFuture = imap.login(mUser, mPassword).exec(); |
282 | syncTransaction.commit(); | 271 | loginFuture.waitForFinished(); |
283 | folderList << folders; | 272 | if (loginFuture.errorCode()) { |
273 | Warning() << "Login failed."; | ||
274 | future.setError(1, "Login failed"); | ||
275 | return; | ||
276 | } else { | ||
277 | Trace() << "Login was successful"; | ||
278 | } | ||
284 | 279 | ||
285 | }); | 280 | QVector<Folder> folderList; |
286 | folderFuture.waitForFinished(); | 281 | auto folderFuture = imap.fetchFolders([this, &imap, &folderList](const QVector<Folder> &folders) { |
287 | if (folderFuture.errorCode()) { | 282 | synchronizeFolders(folders); |
288 | Warning() << "Folder sync failed."; | 283 | commit(); |
289 | future.setError(1, "Folder list sync failed"); | 284 | commitSync(); |
290 | return; | 285 | folderList << folders; |
291 | } else { | ||
292 | Trace() << "Folder sync was successful"; | ||
293 | } | ||
294 | 286 | ||
295 | for (const auto &folder : folderList) { | ||
296 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
297 | // auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadOnly); | ||
298 | |||
299 | //TODO load entity to read sync settings should we have some (if the folder is existing already) | ||
300 | //Note that this will not work if we change any of those settings in the pipeline | ||
301 | // | ||
302 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
303 | // const auto sinkId = resolveRemoteId(ENTITY_TYPE_FOLDER, folder.toUtf8(), syncTransaction); | ||
304 | // const auto found = mainDatabase.contains(sinkId); | ||
305 | // if (found) { | ||
306 | // if (auto current = getLatest(mainDatabase, sinkId, mFolderAdaptorFactory)) { | ||
307 | // | ||
308 | // } | ||
309 | // } | ||
310 | |||
311 | // transaction.commit(); | ||
312 | // syncTransaction.commit(); | ||
313 | |||
314 | QSet<qint64> uids; | ||
315 | auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder, &uids](const QVector<Message> &messages) { | ||
316 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
317 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
318 | Trace() << "Synchronizing mails" << folder.normalizedPath(); | ||
319 | for (const auto &msg : messages) { | ||
320 | uids << msg.uid; | ||
321 | } | ||
322 | synchronizeMails(transaction, syncTransaction, folder.normalizedPath(), messages); | ||
323 | transaction.commit(); | ||
324 | syncTransaction.commit(); | ||
325 | }); | 287 | }); |
326 | messagesFuture.waitForFinished(); | 288 | folderFuture.waitForFinished(); |
327 | if (messagesFuture.errorCode()) { | 289 | if (folderFuture.errorCode()) { |
328 | future.setError(1, "Folder sync failed: " + folder.normalizedPath()); | 290 | Warning() << "Folder sync failed."; |
291 | future.setError(1, "Folder list sync failed"); | ||
329 | return; | 292 | return; |
293 | } else { | ||
294 | Trace() << "Folder sync was successful"; | ||
330 | } | 295 | } |
331 | //Remove what there is to remove | ||
332 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
333 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
334 | synchronizeRemovals(transaction, syncTransaction, folder.normalizedPath(), uids); | ||
335 | transaction.commit(); | ||
336 | syncTransaction.commit(); | ||
337 | Trace() << "Folder synchronized: " << folder.normalizedPath(); | ||
338 | } | ||
339 | 296 | ||
340 | Log() << "Done Synchronizing"; | 297 | for (const auto &folder : folderList) { |
341 | future.setFinished(); | 298 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); |
342 | }); | 299 | // auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadOnly); |
343 | } | 300 | |
301 | //TODO load entity to read sync settings should we have some (if the folder is existing already) | ||
302 | //Note that this will not work if we change any of those settings in the pipeline | ||
303 | // | ||
304 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
305 | // const auto sinkId = resolveRemoteId(ENTITY_TYPE_FOLDER, folder.toUtf8(), syncTransaction); | ||
306 | // const auto found = mainDatabase.contains(sinkId); | ||
307 | // if (found) { | ||
308 | // if (auto current = getLatest(mainDatabase, sinkId, mFolderAdaptorFactory)) { | ||
309 | // | ||
310 | // } | ||
311 | // } | ||
312 | |||
313 | // transaction.commit(); | ||
314 | // syncTransaction.commit(); | ||
315 | |||
316 | QSet<qint64> uids; | ||
317 | auto messagesFuture = imap.fetchMessages(folder, [this, folder, &uids](const QVector<Message> &messages) { | ||
318 | Trace() << "Synchronizing mails" << folder.normalizedPath(); | ||
319 | for (const auto &msg : messages) { | ||
320 | uids << msg.uid; | ||
321 | } | ||
322 | synchronizeMails(folder.normalizedPath(), messages); | ||
323 | commit(); | ||
324 | commitSync(); | ||
325 | }); | ||
326 | messagesFuture.waitForFinished(); | ||
327 | if (messagesFuture.errorCode()) { | ||
328 | future.setError(1, "Folder sync failed: " + folder.normalizedPath()); | ||
329 | return; | ||
330 | } | ||
331 | //Remove what there is to remove | ||
332 | synchronizeRemovals(folder.normalizedPath(), uids); | ||
333 | commit(); | ||
334 | commitSync(); | ||
335 | Trace() << "Folder synchronized: " << folder.normalizedPath(); | ||
336 | } | ||
337 | |||
338 | Log() << "Done Synchronizing"; | ||
339 | future.setFinished(); | ||
340 | }); | ||
341 | } | ||
344 | 342 | ||
345 | KAsync::Job<void> ImapResource::replay(Sink::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) | 343 | public: |
344 | QString mServer; | ||
345 | int mPort; | ||
346 | QString mUser; | ||
347 | QString mPassword; | ||
348 | QByteArray mResourceInstanceIdentifier; | ||
349 | }; | ||
350 | |||
351 | ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | ||
352 | : Sink::GenericResource(PLUGIN_NAME, instanceIdentifier, pipeline) | ||
346 | { | 353 | { |
347 | //TODO implement | 354 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); |
348 | return KAsync::null<void>(); | 355 | mServer = config.value("server").toString(); |
356 | mPort = config.value("port").toInt(); | ||
357 | mUser = config.value("user").toString(); | ||
358 | mPassword = config.value("password").toString(); | ||
359 | |||
360 | auto synchronizer = QSharedPointer<ImapSynchronizer>::create(PLUGIN_NAME, instanceIdentifier); | ||
361 | synchronizer->mServer = mServer; | ||
362 | synchronizer->mPort = mPort; | ||
363 | synchronizer->mUser = mUser; | ||
364 | synchronizer->mPassword = mPassword; | ||
365 | synchronizer->mResourceInstanceIdentifier = instanceIdentifier; | ||
366 | setupSynchronizer(synchronizer); | ||
367 | auto changereplay = QSharedPointer<Sink::NullChangeReplay>::create(); | ||
368 | // auto changereplay = QSharedPointer<ImapWriteback>::create(PLUGIN_NAME, instanceIdentifier); | ||
369 | // changereplay->mServer = mServer; | ||
370 | // changereplay->mPort = mPort; | ||
371 | // changereplay->mUser = mUser; | ||
372 | // changereplay->mPassword = mPassword; | ||
373 | setupChangereplay(changereplay); | ||
374 | |||
375 | setupPreprocessors(ENTITY_TYPE_MAIL, QVector<Sink::Preprocessor*>() << new MailPropertyExtractor << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
376 | setupPreprocessors(ENTITY_TYPE_FOLDER, QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
349 | } | 377 | } |
350 | 378 | ||
351 | void ImapResource::removeFromDisk(const QByteArray &instanceIdentifier) | 379 | void ImapResource::removeFromDisk(const QByteArray &instanceIdentifier) |
@@ -356,7 +384,98 @@ void ImapResource::removeFromDisk(const QByteArray &instanceIdentifier) | |||
356 | 384 | ||
357 | KAsync::Job<void> ImapResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | 385 | KAsync::Job<void> ImapResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) |
358 | { | 386 | { |
359 | //TODO | 387 | auto synchronizationStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Sink::Storage::ReadOnly); |
388 | auto synchronizationTransaction = synchronizationStore->createTransaction(Sink::Storage::ReadOnly); | ||
389 | |||
390 | auto mainStore = QSharedPointer<Sink::Storage>::create(Sink::storageLocation(), mResourceInstanceIdentifier, Sink::Storage::ReadOnly); | ||
391 | auto transaction = mainStore->createTransaction(Sink::Storage::ReadOnly); | ||
392 | |||
393 | auto entityStore = QSharedPointer<Sink::EntityStore>::create(mResourceType, mResourceInstanceIdentifier, transaction); | ||
394 | auto syncStore = QSharedPointer<Sink::RemoteIdMap>::create(synchronizationTransaction); | ||
395 | |||
396 | Trace() << "Inspecting " << inspectionType << domainType << entityId << property << expectedValue; | ||
397 | |||
398 | // if (domainType == ENTITY_TYPE_MAIL) { | ||
399 | // const auto mail = entityStore->read<Sink::ApplicationDomain::Mail>(entityId); | ||
400 | // const auto folder = entityStore->read<Sink::ApplicationDomain::Folder>(mail.getFolder()); | ||
401 | // const auto folderRemoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, mail.getFolder()); | ||
402 | // const auto mailRemoteId = syncStore->resolveLocalId(ENTITY_TYPE_MAIL, mail.identifier()); | ||
403 | // // const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | ||
404 | // ImapServerProxy imap(mServer, mPort); | ||
405 | // imap.login(mUser, mPassword).exec().waitForFinished(); | ||
406 | // imap.select(folderRemoteId).exec().waitForFinished(); | ||
407 | // KIMAP::ImapSet set; | ||
408 | // set.add(mailRemoteId.toLongLong()); | ||
409 | // KIMAP::FetchJob::FetchScope scope; | ||
410 | // scope.mode = KIMAP::FetchJob::FetchScope::Full; | ||
411 | // imap.fetch(set, scope, [](const QVector<Imap::Message> &messages) { | ||
412 | // | ||
413 | // }).exec().waitForFinished(); | ||
414 | // | ||
415 | // if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { | ||
416 | // if (property == "unread") { | ||
417 | // // const auto flags = KPIM::Maildir::readEntryFlags(filePath.split('/').last()); | ||
418 | // // if (expectedValue.toBool() && (flags & KPIM::Maildir::Seen)) { | ||
419 | // // return KAsync::error<void>(1, "Expected unread but couldn't find it."); | ||
420 | // // } | ||
421 | // // if (!expectedValue.toBool() && !(flags & KPIM::Maildir::Seen)) { | ||
422 | // // return KAsync::error<void>(1, "Expected read but couldn't find it."); | ||
423 | // // } | ||
424 | // return KAsync::null<void>(); | ||
425 | // } | ||
426 | // if (property == "subject") { | ||
427 | // // KMime::Message *msg = new KMime::Message; | ||
428 | // // msg->setHead(KMime::CRLFtoLF(KPIM::Maildir::readEntryHeadersFromFile(filePath))); | ||
429 | // // msg->parse(); | ||
430 | // // | ||
431 | // // if (msg->subject(true)->asUnicodeString() != expectedValue.toString()) { | ||
432 | // // return KAsync::error<void>(1, "Subject not as expected: " + msg->subject(true)->asUnicodeString()); | ||
433 | // // } | ||
434 | // return KAsync::null<void>(); | ||
435 | // } | ||
436 | // } | ||
437 | // if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { | ||
438 | // // if (QFileInfo(filePath).exists() != expectedValue.toBool()) { | ||
439 | // // return KAsync::error<void>(1, "Wrong file existence: " + filePath); | ||
440 | // // } | ||
441 | // } | ||
442 | // } | ||
443 | // if (domainType == ENTITY_TYPE_FOLDER) { | ||
444 | // const auto remoteId = syncStore->resolveLocalId(ENTITY_TYPE_FOLDER, entityId); | ||
445 | // const auto folder = entityStore->read<Sink::ApplicationDomain::Folder>(entityId); | ||
446 | // | ||
447 | // if (inspectionType == Sink::ResourceControl::Inspection::CacheIntegrityInspectionType) { | ||
448 | // // Warning() << "Inspecting cache integrity" << remoteId; | ||
449 | // // if (!QDir(remoteId).exists()) { | ||
450 | // // return KAsync::error<void>(1, "The directory is not existing: " + remoteId); | ||
451 | // // } | ||
452 | // // | ||
453 | // // int expectedCount = 0; | ||
454 | // // Index index("mail.index.folder", transaction); | ||
455 | // // index.lookup(entityId, [&](const QByteArray &sinkId) { | ||
456 | // // expectedCount++; | ||
457 | // // }, | ||
458 | // // [&](const Index::Error &error) { | ||
459 | // // Warning() << "Error in index: " << error.message << property; | ||
460 | // // }); | ||
461 | // // | ||
462 | // // QDir dir(remoteId + "/cur"); | ||
463 | // // const QFileInfoList list = dir.entryInfoList(QDir::Files); | ||
464 | // // if (list.size() != expectedCount) { | ||
465 | // // return KAsync::error<void>(1, QString("Wrong number of files; found %1 instead of %2.").arg(list.size()).arg(expectedCount)); | ||
466 | // // } | ||
467 | // // if (inspectionType == Sink::ResourceControl::Inspection::ExistenceInspectionType) { | ||
468 | // // if (!remoteId.endsWith(folder.getName().toUtf8())) { | ||
469 | // // return KAsync::error<void>(1, "Wrong folder name: " + remoteId); | ||
470 | // // } | ||
471 | // // //TODO we shouldn't use the remoteId here to figure out the path, it could be gone/changed already | ||
472 | // // if (QDir(remoteId).exists() != expectedValue.toBool()) { | ||
473 | // // return KAsync::error<void>(1, "Wrong folder existence: " + remoteId); | ||
474 | // // } | ||
475 | // // } | ||
476 | // } | ||
477 | // | ||
478 | // } | ||
360 | return KAsync::null<void>(); | 479 | return KAsync::null<void>(); |
361 | } | 480 | } |
362 | 481 | ||
@@ -377,3 +496,8 @@ void ImapResourceFactory::registerFacades(Sink::FacadeFactory &factory) | |||
377 | factory.registerFacade<Sink::ApplicationDomain::Folder, ImapResourceFolderFacade>(PLUGIN_NAME); | 496 | factory.registerFacade<Sink::ApplicationDomain::Folder, ImapResourceFolderFacade>(PLUGIN_NAME); |
378 | } | 497 | } |
379 | 498 | ||
499 | void ImapResourceFactory::registerAdaptorFactories(Sink::AdaptorFactoryRegistry ®istry) | ||
500 | { | ||
501 | registry.registerFactory<Sink::ApplicationDomain::Mail, ImapMailAdaptorFactory>(PLUGIN_NAME); | ||
502 | registry.registerFactory<Sink::ApplicationDomain::Folder, ImapFolderAdaptorFactory>(PLUGIN_NAME); | ||
503 | } | ||