diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-18 10:38:36 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-18 10:38:36 +0100 |
commit | 23e807c133a23f924d56bc860aa34f62f09109ff (patch) | |
tree | 92d8584902d1cc902c8a1b8d7464427035257ccf /examples/maildirresource/maildirresource.cpp | |
parent | 765f27cf52497bc401579db38f0011d90fb75cbb (diff) | |
download | sink-23e807c133a23f924d56bc860aa34f62f09109ff.tar.gz sink-23e807c133a23f924d56bc860aa34f62f09109ff.zip |
Detect modifications and removals on folders in the maildirresource
Diffstat (limited to 'examples/maildirresource/maildirresource.cpp')
-rw-r--r-- | examples/maildirresource/maildirresource.cpp | 139 |
1 files changed, 111 insertions, 28 deletions
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 54a73e9..10e9046 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp | |||
@@ -23,6 +23,8 @@ | |||
23 | #include "pipeline.h" | 23 | #include "pipeline.h" |
24 | #include "mail_generated.h" | 24 | #include "mail_generated.h" |
25 | #include "createentity_generated.h" | 25 | #include "createentity_generated.h" |
26 | #include "modifyentity_generated.h" | ||
27 | #include "deleteentity_generated.h" | ||
26 | #include "domainadaptor.h" | 28 | #include "domainadaptor.h" |
27 | #include "resourceconfig.h" | 29 | #include "resourceconfig.h" |
28 | #include "commands.h" | 30 | #include "commands.h" |
@@ -64,15 +66,27 @@ MaildirResource::MaildirResource(const QByteArray &instanceIdentifier, const QSh | |||
64 | QString MaildirResource::resolveRemoteId(const QByteArray &bufferType, const QString &remoteId, Akonadi2::Storage::Transaction &transaction) | 66 | QString MaildirResource::resolveRemoteId(const QByteArray &bufferType, const QString &remoteId, Akonadi2::Storage::Transaction &transaction) |
65 | { | 67 | { |
66 | //Lookup local id for remote id, or insert a new pair otherwise | 68 | //Lookup local id for remote id, or insert a new pair otherwise |
67 | auto remoteIdWithType = bufferType + remoteId.toUtf8(); | 69 | Index index("rid.mapping." + bufferType, transaction); |
68 | QByteArray akonadiId = Index("rid.mapping", transaction).lookup(remoteIdWithType); | 70 | Index localIndex("localid.mapping." + bufferType, transaction); |
71 | QByteArray akonadiId = index.lookup(remoteId.toUtf8()); | ||
69 | if (akonadiId.isEmpty()) { | 72 | if (akonadiId.isEmpty()) { |
70 | akonadiId = QUuid::createUuid().toString().toUtf8(); | 73 | akonadiId = QUuid::createUuid().toString().toUtf8(); |
71 | Index("rid.mapping", transaction).add(remoteIdWithType, akonadiId); | 74 | index.add(remoteId.toUtf8(), akonadiId); |
75 | localIndex.add(akonadiId, remoteId.toUtf8()); | ||
72 | } | 76 | } |
73 | return akonadiId; | 77 | return akonadiId; |
74 | } | 78 | } |
75 | 79 | ||
80 | QString MaildirResource::resolveLocalId(const QByteArray &bufferType, const QByteArray &localId, Akonadi2::Storage::Transaction &transaction) | ||
81 | { | ||
82 | Index index("localid.mapping." + bufferType, transaction); | ||
83 | QByteArray remoteId = index.lookup(localId); | ||
84 | if (remoteId.isEmpty()) { | ||
85 | Warning() << "Couldn't find the local id"; | ||
86 | } | ||
87 | return remoteId; | ||
88 | } | ||
89 | |||
76 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) | 90 | static QStringList listRecursive( const QString &root, const KPIM::Maildir &dir ) |
77 | { | 91 | { |
78 | QStringList list; | 92 | QStringList list; |
@@ -114,6 +128,50 @@ static void createEntity(const QByteArray &akonadiId, const QByteArray &bufferTy | |||
114 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | 128 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); |
115 | } | 129 | } |
116 | 130 | ||
131 | static void modifyEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, Akonadi2::ApplicationDomain::ApplicationDomainType &domainObject, DomainTypeAdaptorFactoryInterface &adaptorFactory, std::function<void(const QByteArray &)> callback) | ||
132 | { | ||
133 | flatbuffers::FlatBufferBuilder entityFbb; | ||
134 | adaptorFactory.createBuffer(domainObject, entityFbb); | ||
135 | flatbuffers::FlatBufferBuilder fbb; | ||
136 | auto entityId = fbb.CreateString(akonadiId.toStdString()); | ||
137 | //This is the resource type and not the domain type | ||
138 | auto type = fbb.CreateString(bufferType.toStdString()); | ||
139 | auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); | ||
140 | //TODO removals | ||
141 | auto location = Akonadi2::Commands::CreateModifyEntity(fbb, revision, entityId, 0, type, delta); | ||
142 | Akonadi2::Commands::FinishModifyEntityBuffer(fbb, location); | ||
143 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | ||
144 | } | ||
145 | |||
146 | static void deleteEntity(const QByteArray &akonadiId, qint64 revision, const QByteArray &bufferType, std::function<void(const QByteArray &)> callback) | ||
147 | { | ||
148 | flatbuffers::FlatBufferBuilder fbb; | ||
149 | auto entityId = fbb.CreateString(akonadiId.toStdString()); | ||
150 | //This is the resource type and not the domain type | ||
151 | auto type = fbb.CreateString(bufferType.toStdString()); | ||
152 | auto location = Akonadi2::Commands::CreateDeleteEntity(fbb, revision, entityId, type); | ||
153 | Akonadi2::Commands::FinishDeleteEntityBuffer(fbb, location); | ||
154 | callback(QByteArray::fromRawData(reinterpret_cast<char const *>(fbb.GetBufferPointer()), fbb.GetSize())); | ||
155 | } | ||
156 | |||
157 | static QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> getLatest(const Akonadi2::Storage::NamedDatabase &db, const QByteArray &uid, DomainTypeAdaptorFactoryInterface &adaptorFactory) | ||
158 | { | ||
159 | QSharedPointer<Akonadi2::ApplicationDomain::BufferAdaptor> current; | ||
160 | db.findLatest(uid, [¤t, &adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { | ||
161 | Akonadi2::EntityBuffer buffer(const_cast<const char *>(data.data()), data.size()); | ||
162 | if (!buffer.isValid()) { | ||
163 | Warning() << "Read invalid buffer from disk"; | ||
164 | } else { | ||
165 | current = adaptorFactory.createAdaptor(buffer.entity()); | ||
166 | } | ||
167 | return false; | ||
168 | }, | ||
169 | [](const Akonadi2::Storage::Error &error) { | ||
170 | Warning() << "Failed to read current value from storage: " << error.message; | ||
171 | }); | ||
172 | return current; | ||
173 | } | ||
174 | |||
117 | void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction) | 175 | void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transaction) |
118 | { | 176 | { |
119 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; | 177 | const QByteArray bufferType = ENTITY_TYPE_FOLDER; |
@@ -122,41 +180,66 @@ void MaildirResource::synchronizeFolders(Akonadi2::Storage::Transaction &transac | |||
122 | 180 | ||
123 | Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite); | 181 | Akonadi2::Storage store(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".synchronization", Akonadi2::Storage::ReadWrite); |
124 | auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite); | 182 | auto synchronizationTransaction = store.createTransaction(Akonadi2::Storage::ReadWrite); |
125 | for (const auto folder : folderList) { | 183 | auto mainDatabase = transaction.openDatabase(bufferType + ".main"); |
126 | const auto remoteId = folder.toUtf8(); | 184 | |
185 | //TODO Instead of iterating over all entries in the database, which can also pick up the same item multiple times, | ||
186 | //we should rather iterate over an index that contains every uid exactly once. The remoteId index would be such an index, | ||
187 | //but we currently fail to iterate over all entries in an index it seems. | ||
188 | // auto remoteIds = synchronizationTransaction.openDatabase("rid.mapping." + bufferType, std::function<void(const Akonadi2::Storage::Error &)>(), true); | ||
189 | mainDatabase.scan("", [&folderList, this, &transaction, bufferType, &synchronizationTransaction](const QByteArray &key, const QByteArray &) { | ||
190 | auto akonadiId = Akonadi2::Storage::uidFromKey(key); | ||
191 | const auto remoteId = resolveLocalId(bufferType, akonadiId, synchronizationTransaction); | ||
192 | if (!folderList.contains(remoteId)) { | ||
193 | Trace() << "Found a removed entity: " << akonadiId; | ||
194 | deleteEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, [this](const QByteArray &buffer) { | ||
195 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::DeleteEntityCommand, buffer); | ||
196 | }); | ||
197 | } | ||
198 | return true; | ||
199 | }, | ||
200 | [](const Akonadi2::Storage::Error &error) { | ||
201 | }); | ||
202 | |||
203 | for (const auto folderPath : folderList) { | ||
204 | const auto remoteId = folderPath.toUtf8(); | ||
127 | Trace() << "Processing folder " << remoteId; | 205 | Trace() << "Processing folder " << remoteId; |
128 | auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction); | 206 | const auto akonadiId = resolveRemoteId(bufferType, remoteId, synchronizationTransaction).toLatin1(); |
207 | const auto found = mainDatabase.contains(akonadiId); | ||
129 | 208 | ||
130 | bool found = false; | 209 | KPIM::Maildir md(folderPath, folderPath == mMaildirPath); |
131 | transaction.openDatabase(bufferType + ".main").scan(akonadiId.toUtf8(), [&found](const QByteArray &, const QByteArray &) -> bool { | ||
132 | found = true; | ||
133 | return false; | ||
134 | }, [this](const Akonadi2::Storage::Error &error) { | ||
135 | }, true); | ||
136 | 210 | ||
137 | if (!found) { //A new entity | 211 | Akonadi2::ApplicationDomain::Folder folder; |
138 | KPIM::Maildir md(folder, folder == mMaildirPath); | 212 | folder.setProperty("name", md.name()); |
139 | 213 | folder.setProperty("icon", "folder"); | |
140 | Akonadi2::ApplicationDomain::Folder folder; | 214 | if (!md.isRoot()) { |
141 | folder.setProperty("name", md.name()); | 215 | folder.setProperty("parent", resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), synchronizationTransaction).toLatin1()); |
142 | folder.setProperty("icon", "folder"); | 216 | } |
143 | if (!md.isRoot()) { | ||
144 | Trace() << "subfolder parent: " << md.parent().path(); | ||
145 | auto akonadiId = resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path(), synchronizationTransaction); | ||
146 | folder.setProperty("parent", akonadiId); | ||
147 | } | ||
148 | 217 | ||
218 | if (!found) { | ||
149 | Trace() << "Found a new entity: " << remoteId; | 219 | Trace() << "Found a new entity: " << remoteId; |
150 | createEntity(akonadiId.toLatin1(), bufferType, folder, *mFolderAdaptorFactory, [this](const QByteArray &buffer) { | 220 | createEntity(akonadiId, bufferType, folder, *mFolderAdaptorFactory, [this](const QByteArray &buffer) { |
151 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer); | 221 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, buffer); |
152 | }); | 222 | }); |
153 | |||
154 | } else { //modification | 223 | } else { //modification |
155 | Trace() << "Found a modified entity: " << remoteId; | 224 | if (auto current = getLatest(mainDatabase, akonadiId, *mFolderAdaptorFactory)) { |
156 | //TODO diff and create modification if necessary | 225 | bool changed = false; |
226 | for (const auto &property : folder.changedProperties()) { | ||
227 | if (folder.getProperty(property) != current->getProperty(property)) { | ||
228 | Trace() << "Property changed " << akonadiId << property; | ||
229 | changed = true; | ||
230 | } | ||
231 | } | ||
232 | if (changed) { | ||
233 | Trace() << "Found a modified entity: " << remoteId; | ||
234 | modifyEntity(akonadiId, Akonadi2::Storage::maxRevision(transaction), bufferType, folder, *mFolderAdaptorFactory, [this](const QByteArray &buffer) { | ||
235 | enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::ModifyEntityCommand, buffer); | ||
236 | }); | ||
237 | } | ||
238 | } else { | ||
239 | Warning() << "Failed to get current entity"; | ||
240 | } | ||
157 | } | 241 | } |
158 | } | 242 | } |
159 | //TODO find items to remove | ||
160 | } | 243 | } |
161 | 244 | ||
162 | void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transaction, const QString &path) | 245 | void MaildirResource::synchronizeMails(Akonadi2::Storage::Transaction &transaction, const QString &path) |