summaryrefslogtreecommitdiffstats
path: root/examples/maildirresource/maildirresource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/maildirresource/maildirresource.cpp')
-rw-r--r--examples/maildirresource/maildirresource.cpp284
1 files changed, 284 insertions, 0 deletions
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp
new file mode 100644
index 0000000..f9cc2a4
--- /dev/null
+++ b/examples/maildirresource/maildirresource.cpp
@@ -0,0 +1,284 @@
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 "maildirresource.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 "domainadaptor.h"
27#include "resourceconfig.h"
28#include "commands.h"
29#include "index.h"
30#include "log.h"
31#include "domain/mail.h"
32#include "definitions.h"
33#include "facadefactory.h"
34#include "indexupdater.h"
35#include "libmaildir/maildir.h"
36#include <QDate>
37#include <QUuid>
38#include <QDir>
39#include <QDirIterator>
40#include <KMime/KMime/KMimeMessage>
41
42//This is the resources entity type, and not the domain type
43#define ENTITY_TYPE_MAIL "mail"
44#define ENTITY_TYPE_FOLDER "folder"
45
46MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSharedPointer<Akonadi2::Pipeline> &pipeline)
47 : Akonadi2::GenericResource(instanceIdentifier, pipeline)
48{
49 addType(ENTITY_TYPE_MAIL, QSharedPointer<MaildirMailAdaptorFactory>::create(),
50 QVector<Akonadi2::Preprocessor*>() << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Mail>);
51 addType(ENTITY_TYPE_MAIL, QSharedPointer<MaildirMailAdaptorFactory>::create(),
52 QVector<Akonadi2::Preprocessor*>() << new DefaultIndexUpdater<Akonadi2::ApplicationDomain::Folder>);
53 auto config = ResourceConfig::getConfiguration(instanceIdentifier);
54 mMaildirPath = config.value("path").toString();
55}
56
57QString MaildirResource::resolveRemoteId(const QByteArray &bufferType, const QString &remoteId, Akonadi2::Storage::Transaction &transaction)
58{
59 //Lookup local id for remote id, or insert a new pair otherwise
60 auto remoteIdWithType = bufferType + remoteId.toUtf8();
61 QByteArray akonadiId = Index("rid.mapping", transaction).lookup(remoteIdWithType);
62 if (akonadiId.isEmpty()) {
63 akonadiId = QUuid::createUuid().toString().toUtf8();
64 Index("rid.mapping", transaction).add(remoteIdWithType, akonadiId);
65 }
66 return akonadiId;
67}
68
69static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir )
70{
71 QStringList list;
72 foreach (const QString &sub, dir.subFolderList()) {
73 const KPIM::Maildir md = dir.subFolder(sub);
74 if (!md.isValid()) {
75 continue;
76 }
77 QString path = root + QDir::separator() + sub;
78 list << path;
79 list += listRecursive(path, md );
80 }
81 return list;
82}
83
84QStringList MaildirResource::listAvailableFolders()
85{
86 KPIM::Maildir dir(mMaildirPath, true);
87 if (!dir.isValid()) {
88 return QStringList();
89 }
90 QStringList folderList;
91 folderList << mMaildirPath;
92 folderList += listRecursive(mMaildirPath, dir);
93 return folderList;
94}
95
96void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction)
97{
98 const QString bufferType = ENTITY_TYPE_FOLDER;
99 QStringList folderList = listAvailableFolders();
100 Trace() << "Found folders " << folderList;
101
102 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
103 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
104 Index ridMapping("rid.mapping", synchronizationTransaction);
105 for (const auto folder : folderList) {
106 const auto remoteId = folder.toUtf8();
107 auto akonadiId = resolveRemoteId(bufferType.toUtf8(), remoteId, synchronizationTransaction);
108
109 bool found = false;
110 transaction.openDatabase(bufferType.toUtf8() + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool {
111 found = true;
112 return false;
113 }, [this](const Akonadi2::Storage::Error &error) {
114 }, true);
115
116 if (!found) { //A new entity
117 m_fbb.Clear();
118
119 KPIM::Maildir md(folder);
120
121 flatbuffers::FlatBufferBuilder entityFbb;
122 auto name = m_fbb.CreateString(md.name().toStdString());
123 auto icon = m_fbb.CreateString("folder");
124 flatbuffers::Offset<flatbuffers::String> parent;
125
126 if (!md.isRoot()) {
127 auto akonadiId = resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), transaction);
128 parent = m_fbb.CreateString(akonadiId.toStdString());
129 }
130
131 auto builder = Akonadi2::ApplicationDomain::Buffer::FolderBuilder(m_fbb);
132 builder.add_name(name);
133 if (!md.isRoot()) {
134 builder.add_parent(parent);
135 }
136 builder.add_icon(icon);
137 auto buffer = builder.Finish();
138 Akonadi2::ApplicationDomain::Buffer::FinishFolderBuffer(m_fbb, buffer);
139 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
140
141 flatbuffers::FlatBufferBuilder fbb;
142 //This is the resource type and not the domain type
143 auto entityId = fbb.CreateString(akonadiId.toStdString());
144 auto type = fbb.CreateString(bufferType.toStdString());
145 auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize());
146 auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta);
147 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location);
148
149 Trace() << "Found a new entity: " << remoteId;
150 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()));
151 } else { //modification
152 Trace() << "Found a modified entity: " << remoteId;
153 //TODO diff and create modification if necessary
154 }
155 }
156 //TODO find items to remove
157}
158
159void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transaction, const QString &path)
160{
161 Trace() << "Synchronizing mails" << path;
162 const QString bufferType = ENTITY_TYPE_MAIL;
163
164 KPIM::Maildir maildir(path, true);
165 if (!maildir.isValid()) {
166 Warning() << "Failed to sync folder " << maildir.lastError();
167 return;
168 }
169
170 auto listingPath = maildir.pathToCurrent();
171 auto entryIterator = QSharedPointer<QDirIterator>::create(listingPath, QDir::Files);
172 Trace() << "Looking into " << maildir.pathToNew();
173
174 QFileInfo entryInfo;
175
176 Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite);
177 auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
178 Index ridMapping("rid.mapping", synchronizationTransaction);
179
180 while (entryIterator->hasNext()) {
181 QString filePath = entryIterator->next();
182 QString fileName = entryIterator->fileName();
183
184 const auto remoteId = fileName.toUtf8();
185 auto akonadiId = resolveRemoteId(bufferType.toUtf8(), remoteId, synchronizationTransaction);
186
187 bool found = false;
188 transaction.openDatabase(bufferType.toUtf8() + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool {
189 found = true;
190 return false;
191 }, [this](const Akonadi2::Storage::Error &error) {
192 }, true);
193
194 if (!found) { //A new entity
195 m_fbb.Clear();
196
197 KMime::Message *msg = new KMime::Message;
198 auto filepath = listingPath + QDir::separator() + fileName;
199 msg->setHead(KMime::CRLFtoLF(maildir.readEntryHeadersFromFile(filepath)));
200 msg->parse();
201
202 const auto flags = maildir.readEntryFlags(fileName);
203
204 Trace() << "Found a mail " << filePath << fileName << msg->subject(true)->asUnicodeString();
205 flatbuffers::FlatBufferBuilder entityFbb;
206 auto subject = m_fbb.CreateString(msg->subject(true)->asUnicodeString().toStdString());
207 auto sender = m_fbb.CreateString(msg->from(true)->asUnicodeString().toStdString());
208 auto senderName = m_fbb.CreateString(msg->from(true)->asUnicodeString().toStdString());
209 auto date = m_fbb.CreateString(msg->date(true)->dateTime().toString().toStdString());
210 auto folder = m_fbb.CreateString(resolveRemoteId(ENTITY_TYPE_FOLDER, path, transaction).toStdString());
211 auto mimeMessage = m_fbb.CreateString(filepath.toStdString());
212
213 auto builder = Akonadi2::ApplicationDomain::Buffer::MailBuilder(m_fbb);
214 builder.add_subject(subject);
215 builder.add_sender(sender);
216 builder.add_senderName(senderName);
217 builder.add_unread(!(flags & KPIM::Maildir::Seen));
218 builder.add_important(flags & KPIM::Maildir::Flagged);
219 builder.add_date(date);
220 builder.add_folder(folder);
221 builder.add_mimeMessage(mimeMessage);
222 auto buffer = builder.Finish();
223 Akonadi2::ApplicationDomain::Buffer::FinishMailBuffer(m_fbb, buffer);
224 Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize());
225
226 flatbuffers::FlatBufferBuilder fbb;
227 //This is the resource type and not the domain type
228 auto entityId = fbb.CreateString(akonadiId.toStdString());
229 auto type = fbb.CreateString(bufferType.toStdString());
230 auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize());
231 auto location = Akonadi2::Commands::CreateCreateEntity(fbb, entityId, type, delta);
232 Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location);
233
234 Trace() << "Found a new entity: " << remoteId;
235 enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize()));
236 } else { //modification
237 Trace() << "Found a modified entity: " << remoteId;
238 //TODO diff and create modification if necessary
239 }
240 }
241 //TODO find items to remove
242}
243
244KAsync::Job<void> MaildirResource::synchronizeWithSource()
245{
246 Log() << " Synchronizing";
247 return KAsync::start<void>([this]() {
248 auto transaction = Akonadi2::Storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier, Akonadi2::Storage::ReadOnly).createTransaction(Akonadi2::Storage::ReadOnly);
249 synchronizeFolders(transaction);
250 for (const auto &folder : listAvailableFolders()) {
251 synchronizeMails(transaction, folder);
252 }
253 });
254}
255
256KAsync::Job<void> MaildirResource::replay(const QByteArray &type, const QByteArray &key, const QByteArray &value)
257{
258 Trace() << "Replaying " << key;
259 return KAsync::null<void>();
260}
261
262void MaildirResource::removeFromDisk(const QByteArray &instanceIdentifier)
263{
264 GenericResource::removeFromDisk(instanceIdentifier);
265 Akonadi2::Storage(Akonadi2::storageLocation(), instanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite).removeFromDisk();
266}
267
268MaildirResourceFactory::MaildirResourceFactory(QObject *parent)
269 : Akonadi2::ResourceFactory(parent)
270{
271
272}
273
274Akonadi2::Resource *MaildirResourceFactory::createResource(const QByteArray &instanceIdentifier)
275{
276 return new MaildirResource(instanceIdentifier);
277}
278
279void MaildirResourceFactory::registerFacades(Akonadi2::FacadeFactory &factory)
280{
281 factory.registerFacade<Akonadi2::ApplicationDomain::Mail, MaildirResourceMailFacade>(PLUGIN_NAME);
282 factory.registerFacade<Akonadi2::ApplicationDomain::Folder, MaildirResourceFolderFacade>(PLUGIN_NAME);
283}
284