diff options
Diffstat (limited to 'examples/imapresource/imapresource.cpp')
-rw-r--r-- | examples/imapresource/imapresource.cpp | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/examples/imapresource/imapresource.cpp b/examples/imapresource/imapresource.cpp new file mode 100644 index 0000000..baa88b9 --- /dev/null +++ b/examples/imapresource/imapresource.cpp | |||
@@ -0,0 +1,277 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "imapresource.h" | ||
21 | #include "facade.h" | ||
22 | #include "entitybuffer.h" | ||
23 | #include "pipeline.h" | ||
24 | #include "mail_generated.h" | ||
25 | #include "createentity_generated.h" | ||
26 | #include "modifyentity_generated.h" | ||
27 | #include "deleteentity_generated.h" | ||
28 | #include "domainadaptor.h" | ||
29 | #include "resourceconfig.h" | ||
30 | #include "commands.h" | ||
31 | #include "index.h" | ||
32 | #include "log.h" | ||
33 | #include "domain/mail.h" | ||
34 | #include "definitions.h" | ||
35 | #include "facadefactory.h" | ||
36 | #include "indexupdater.h" | ||
37 | #include "inspection.h" | ||
38 | #include <QDate> | ||
39 | #include <QUuid> | ||
40 | #include <QDir> | ||
41 | #include <QDirIterator> | ||
42 | |||
43 | #include "imapserverproxy.h" | ||
44 | |||
45 | //This is the resources entity type, and not the domain type | ||
46 | #define ENTITY_TYPE_MAIL "mail" | ||
47 | #define ENTITY_TYPE_FOLDER "folder" | ||
48 | |||
49 | #undef DEBUG_AREA | ||
50 | #define DEBUG_AREA "resource.imap" | ||
51 | |||
52 | |||
53 | ImapResource::ImapResource(const QByteArray &instanceIdentifier, const QSharedPointer<Sink::Pipeline> &pipeline) | ||
54 | : Sink::GenericResource(instanceIdentifier, pipeline), | ||
55 | mMailAdaptorFactory(QSharedPointer<ImapMailAdaptorFactory>::create()), | ||
56 | mFolderAdaptorFactory(QSharedPointer<ImapFolderAdaptorFactory>::create()) | ||
57 | { | ||
58 | auto config = ResourceConfig::getConfiguration(instanceIdentifier); | ||
59 | mServer = config.value("server").toString(); | ||
60 | mPort = config.value("port").toInt(); | ||
61 | |||
62 | // auto folderUpdater = new FolderUpdater(QByteArray()); | ||
63 | addType(ENTITY_TYPE_MAIL, mMailAdaptorFactory, | ||
64 | QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Mail>); | ||
65 | addType(ENTITY_TYPE_FOLDER, mFolderAdaptorFactory, | ||
66 | QVector<Sink::Preprocessor*>() << new DefaultIndexUpdater<Sink::ApplicationDomain::Folder>); | ||
67 | } | ||
68 | |||
69 | QByteArray ImapResource::createFolder(const QString &folderPath, const QByteArray &icon, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | ||
70 | { | ||
71 | auto remoteId = folderPath.toUtf8(); | ||
72 | auto bufferType = ENTITY_TYPE_FOLDER; | ||
73 | Sink::ApplicationDomain::Folder folder; | ||
74 | folder.setProperty("name", folderPath.split('/').last()); | ||
75 | folder.setProperty("icon", icon); | ||
76 | |||
77 | // if (!md.isRoot()) { | ||
78 | // folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8(), synchronizationTransaction)); | ||
79 | // } | ||
80 | createOrModify(transaction, synchronizationTransaction, *mFolderAdaptorFactory, bufferType, remoteId, folder); | ||
81 | return remoteId; | ||
82 | } | ||
83 | |||
84 | void ImapResource::synchronizeFolders(const QStringList &folderList, Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction) | ||
85 | { | ||
86 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | ||
87 | Trace() << "Found folders " << folderList; | ||
88 | |||
89 | scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
90 | [&bufferType, &transaction](const std::function<void(const QByteArray &)> &callback) { | ||
91 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | ||
92 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
93 | //but we currently fail to iterate over all entries in an index it seems. | ||
94 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Sink::Storage::Error &)>(), true); | ||
95 | auto mainDatabase = Sink::Storage::mainDatabase(transaction, bufferType); | ||
96 | mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &) { | ||
97 | callback(key); | ||
98 | return true; | ||
99 | }); | ||
100 | }, | ||
101 | [&folderList](const QByteArray &remoteId) -> bool { | ||
102 | return folderList.contains(remoteId); | ||
103 | } | ||
104 | ); | ||
105 | |||
106 | for (const auto folderPath : folderList) { | ||
107 | createFolder(folderPath, "folder", transaction, synchronizationTransaction); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | void ImapResource::synchronizeMails(Sink::Storage::Transaction &transaction, Sink::Storage::Transaction &synchronizationTransaction, const QString &path, const QVector<Message> &messages) | ||
112 | { | ||
113 | auto time = QSharedPointer<QTime>::create(); | ||
114 | time->start(); | ||
115 | const QByteArray bufferType = ENTITY_TYPE_MAIL; | ||
116 | |||
117 | |||
118 | Trace() << "Importing new mail."; | ||
119 | |||
120 | // Trace() << "Looking into " << listingPath; | ||
121 | |||
122 | const auto folderLocalId = resolveRemoteId(ENTITY_TYPE_FOLDER, path.toUtf8(), synchronizationTransaction); | ||
123 | |||
124 | //This is not a full listing | ||
125 | // auto property = "folder"; | ||
126 | // scanForRemovals(transaction, synchronizationTransaction, bufferType, | ||
127 | // [&](const std::function<void(const QByteArray &)> &callback) { | ||
128 | // Index index(bufferType + ".index." + property, transaction); | ||
129 | // index.lookup(folderLocalId, [&](const QByteArray &sinkId) { | ||
130 | // callback(sinkId); | ||
131 | // }, | ||
132 | // [&](const Index::Error &error) { | ||
133 | // Warning() << "Error in index: " << error.message << property; | ||
134 | // }); | ||
135 | // }, | ||
136 | // [](const QByteArray &remoteId) -> bool { | ||
137 | // return QFile(remoteId).exists(); | ||
138 | // } | ||
139 | // ); | ||
140 | |||
141 | mSynchronizerQueue.startTransaction(); | ||
142 | int count = 0; | ||
143 | for (const auto &message : messages) { | ||
144 | count++; | ||
145 | const auto remoteId = path.toUtf8() + "/" + QByteArray::number(message.uid); | ||
146 | |||
147 | Trace() << "Found a mail " << remoteId << message.msg->subject(true)->asUnicodeString() << message.flags; | ||
148 | |||
149 | Sink::ApplicationDomain::Mail mail; | ||
150 | mail.setFolder(folderLocalId); | ||
151 | //FIXME this should come from the mime message, extracted in the pipeline | ||
152 | mail.setExtractedSubject(message.msg->subject(true)->asUnicodeString()); | ||
153 | |||
154 | auto filePath = Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + remoteId; | ||
155 | QDir().mkpath(Sink::resourceStorageLocation(mResourceInstanceIdentifier) + "/" + path.toUtf8()); | ||
156 | QFile file(filePath); | ||
157 | if (!file.open(QIODevice::WriteOnly)) { | ||
158 | Warning() << "Failed to open file for writing: " << file.errorString(); | ||
159 | } | ||
160 | const auto content = message.msg->encodedContent(); | ||
161 | file.write(content); | ||
162 | mail.setMimeMessagePath(filePath); | ||
163 | //FIXME Not sure if these are the actual flags | ||
164 | mail.setUnread(message.flags.contains("\\SEEN")); | ||
165 | mail.setImportant(message.flags.contains("\\FLAGGED")); | ||
166 | |||
167 | createOrModify(transaction, synchronizationTransaction, *mMailAdaptorFactory, bufferType, remoteId, mail); | ||
168 | } | ||
169 | mSynchronizerQueue.commit(); | ||
170 | const auto elapsed = time->elapsed(); | ||
171 | Log() << "Synchronized " << count << " mails in " << path << Sink::Log::TraceTime(elapsed) << " " << elapsed/qMax(count, 1) << " [ms/mail]"; | ||
172 | } | ||
173 | |||
174 | KAsync::Job<void> ImapResource::synchronizeWithSource(Sink::Storage &mainStore, Sink::Storage &synchronizationStore) | ||
175 | { | ||
176 | Log() << " Synchronizing"; | ||
177 | return KAsync::start<void>([this, &mainStore, &synchronizationStore](KAsync::Future<void> future) { | ||
178 | ImapServerProxy imap(mServer, mPort); | ||
179 | QStringList folderList; | ||
180 | // QList<KAsync::Future<void>> waitCondition; | ||
181 | auto folderFuture = imap.fetchFolders([this, &imap, &mainStore, &synchronizationStore, &folderList](const QStringList &folders) { | ||
182 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
183 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
184 | synchronizeFolders(folders, transaction, syncTransaction); | ||
185 | transaction.commit(); | ||
186 | syncTransaction.commit(); | ||
187 | folderList << folders; | ||
188 | |||
189 | }); | ||
190 | folderFuture.waitForFinished(); | ||
191 | if (folderFuture.errorCode()) { | ||
192 | future.setError(1, "Folder list sync failed"); | ||
193 | return; | ||
194 | } | ||
195 | |||
196 | for (const auto &folder : folderList) { | ||
197 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
198 | // auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadOnly); | ||
199 | |||
200 | //TODO load entity to read sync settings should we have some (if the folder is existing already) | ||
201 | //Note that this will not work if we change any of those settings in the pipeline | ||
202 | // | ||
203 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
204 | // const auto sinkId = resolveRemoteId(ENTITY_TYPE_FOLDER, folder.toUtf8(), syncTransaction); | ||
205 | // const auto found = mainDatabase.contains(sinkId); | ||
206 | // if (found) { | ||
207 | // if (auto current = getLatest(mainDatabase, sinkId, mFolderAdaptorFactory)) { | ||
208 | // | ||
209 | // } | ||
210 | // } | ||
211 | |||
212 | // transaction.commit(); | ||
213 | // syncTransaction.commit(); | ||
214 | |||
215 | auto messagesFuture = imap.fetchMessages(folder, [this, &mainStore, &synchronizationStore, folder](const QVector<Message> &messages) { | ||
216 | auto transaction = mainStore.createTransaction(Sink::Storage::ReadOnly); | ||
217 | auto syncTransaction = synchronizationStore.createTransaction(Sink::Storage::ReadWrite); | ||
218 | Trace() << "Synchronizing mails" << folder; | ||
219 | synchronizeMails(transaction, syncTransaction, folder, messages); | ||
220 | transaction.commit(); | ||
221 | syncTransaction.commit(); | ||
222 | }); | ||
223 | messagesFuture.waitForFinished(); | ||
224 | if (messagesFuture.errorCode()) { | ||
225 | future.setError(1, "Folder sync failed: " + folder); | ||
226 | return; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | |||
231 | // auto transaction = mainStore.createTransaction(Sink::Storage::ReadWrite); | ||
232 | // auto mainDatabase = Sink::Storage::mainDatabase(transaction, ENTITY_TYPE_FOLDER); | ||
233 | // mainDatabase.scan("", [&](const QByteArray &key, const QByteArray &data) { | ||
234 | // return true; | ||
235 | // }); | ||
236 | //TODO now fetch all folders and iterate over them and synchronize each one | ||
237 | |||
238 | Log() << "Done Synchronizing"; | ||
239 | future.setFinished(); | ||
240 | }); | ||
241 | } | ||
242 | |||
243 | KAsync::Job<void> ImapResource::replay(Sink::Storage &synchronizationStore, const QByteArray &type, const QByteArray &key, const QByteArray &value) | ||
244 | { | ||
245 | //TODO implement | ||
246 | return KAsync::null<void>(); | ||
247 | } | ||
248 | |||
249 | void ImapResource::removeFromDisk(const QByteArray &instanceIdentifier) | ||
250 | { | ||
251 | GenericResource::removeFromDisk(instanceIdentifier); | ||
252 | Sink::Storage(Sink::storageLocation(), instanceIdentifier + ".synchronization", Sink::Storage::ReadWrite).removeFromDisk(); | ||
253 | } | ||
254 | |||
255 | KAsync::Job<void> ImapResource::inspect(int inspectionType, const QByteArray &inspectionId, const QByteArray &domainType, const QByteArray &entityId, const QByteArray &property, const QVariant &expectedValue) | ||
256 | { | ||
257 | //TODO | ||
258 | return KAsync::null<void>(); | ||
259 | } | ||
260 | |||
261 | ImapResourceFactory::ImapResourceFactory(QObject *parent) | ||
262 | : Sink::ResourceFactory(parent) | ||
263 | { | ||
264 | |||
265 | } | ||
266 | |||
267 | Sink::Resource *ImapResourceFactory::createResource(const QByteArray &instanceIdentifier) | ||
268 | { | ||
269 | return new ImapResource(instanceIdentifier); | ||
270 | } | ||
271 | |||
272 | void ImapResourceFactory::registerFacades(Sink::FacadeFactory &factory) | ||
273 | { | ||
274 | factory.registerFacade<Sink::ApplicationDomain::Mail, ImapResourceMailFacade>(PLUGIN_NAME); | ||
275 | factory.registerFacade<Sink::ApplicationDomain::Folder, ImapResourceFolderFacade>(PLUGIN_NAME); | ||
276 | } | ||
277 | |||