From bbbda3fe9444eba6795a5490da0425cdf8f26361 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Tue, 8 Sep 2015 21:08:54 +0200 Subject: Added support for mails to akonadi and the dummyresource. Adding new types definitely needs to become easier. --- common/CMakeLists.txt | 2 + common/domain/applicationdomaintype.cpp | 12 +++ common/domain/applicationdomaintype.h | 14 +++- common/domain/mail.cpp | 101 +++++++++++++++++++++++ common/domain/mail.fbs | 14 ++++ common/domain/mail.h | 60 ++++++++++++++ common/domainadaptor.h | 1 + common/entitystorage.cpp | 12 +-- common/entitystorage.h | 7 +- common/facade.h | 2 +- common/pipeline.cpp | 64 +++++++++------ common/pipeline.h | 7 +- common/propertymapper.cpp | 6 ++ common/propertymapper.h | 2 + examples/dummyresource/domainadaptor.cpp | 6 ++ examples/dummyresource/domainadaptor.h | 9 +++ examples/dummyresource/dummystore.cpp | 33 +++++++- examples/dummyresource/dummystore.h | 4 +- examples/dummyresource/facade.cpp | 8 ++ examples/dummyresource/facade.h | 7 ++ examples/dummyresource/resourcefactory.cpp | 126 ++++++++++++++++++++++++----- examples/dummyresource/resourcefactory.h | 3 + tests/dummyresourcetest.cpp | 15 ++++ tests/genericfacadebenchmark.cpp | 2 +- tests/genericfacadetest.cpp | 4 +- 25 files changed, 452 insertions(+), 69 deletions(-) create mode 100644 common/domain/mail.cpp create mode 100644 common/domain/mail.fbs create mode 100644 common/domain/mail.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 2f779b5..25ea667 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -34,6 +34,7 @@ set(command_SRCS resourceconfig.cpp domain/applicationdomaintype.cpp domain/event.cpp + domain/mail.cpp ${storage_SRCS}) add_library(${PROJECT_NAME} SHARED ${command_SRCS}) @@ -51,6 +52,7 @@ generate_flatbuffers( commands/synchronize commands/notification domain/event + domain/mail entity metadata queuedcommand diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp index 47ff0c3..3cc075b 100644 --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp @@ -40,6 +40,18 @@ QByteArray getTypeName() return "akonadiresource"; } +template<> +QByteArray getTypeName() +{ + return "mail"; +} + +template<> +QByteArray getTypeName() +{ + return "folder"; +} + } } diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index 29bebcf..e0a6de0 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -112,10 +112,14 @@ struct Calendar : public ApplicationDomainType { using ApplicationDomainType::ApplicationDomainType; }; -class Mail : public ApplicationDomainType { +struct Mail : public ApplicationDomainType { + typedef QSharedPointer Ptr; + using ApplicationDomainType::ApplicationDomainType; }; -class Folder : public ApplicationDomainType { +struct Folder : public ApplicationDomainType { + typedef QSharedPointer Ptr; + using ApplicationDomainType::ApplicationDomainType; }; /** @@ -146,6 +150,12 @@ QByteArray getTypeName(); template<> QByteArray getTypeName(); +template<> +QByteArray getTypeName(); + +template<> +QByteArray getTypeName(); + /** * Type implementation. * diff --git a/common/domain/mail.cpp b/common/domain/mail.cpp new file mode 100644 index 0000000..c52bfe0 --- /dev/null +++ b/common/domain/mail.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "mail.h" + +#include +#include +#include + +#include "../resultset.h" +#include "../index.h" +#include "../storage.h" +#include "../log.h" +#include "../propertymapper.h" +#include "../query.h" +#include "../definitions.h" + +#include "mail_generated.h" + +using namespace Akonadi2::ApplicationDomain; + +ResultSet TypeImplementation::queryIndexes(const Akonadi2::Query &query, const QByteArray &resourceInstanceIdentifier, QSet &appliedFilters, Akonadi2::Storage::Transaction &transaction) +{ + QVector keys; + if (query.propertyFilter.contains("uid")) { + Index uidIndex("mail.index.uid", transaction); + uidIndex.lookup(query.propertyFilter.value("uid").toByteArray(), [&](const QByteArray &value) { + keys << value; + }, + [](const Index::Error &error) { + Warning() << "Error in uid index: " << error.message; + }); + appliedFilters << "uid"; + } + return ResultSet(keys); +} + +void TypeImplementation::index(const Mail &type, Akonadi2::Storage::Transaction &transaction) +{ + const auto uid = type.getProperty("uid"); + if (uid.isValid()) { + Index uidIndex("mail.index.uid", transaction); + uidIndex.add(uid.toByteArray(), type.identifier()); + } +} + +QSharedPointer::Buffer> > TypeImplementation::initializeReadPropertyMapper() +{ + auto propertyMapper = QSharedPointer >::create(); + propertyMapper->addMapping("uid", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->uid()); + }); + propertyMapper->addMapping("sender", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->sender()); + }); + propertyMapper->addMapping("senderName", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->senderName()); + }); + propertyMapper->addMapping("subject", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->subject()); + }); + propertyMapper->addMapping("date", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->date()); + }); + propertyMapper->addMapping("unread", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->unread()); + }); + propertyMapper->addMapping("important", [](Buffer const *buffer) -> QVariant { + return propertyToVariant(buffer->important()); + }); + return propertyMapper; +} + +QSharedPointer::BufferBuilder> > TypeImplementation::initializeWritePropertyMapper() +{ + auto propertyMapper = QSharedPointer >::create(); + // propertyMapper->addMapping("summary", [](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function { + // auto offset = variantToProperty(value, fbb); + // return [offset](BufferBuilder &builder) { builder.add_summary(offset); }; + // }); + propertyMapper->addMapping("uid", [](const QVariant &value, flatbuffers::FlatBufferBuilder &fbb) -> std::function { + auto offset = variantToProperty(value, fbb); + return [offset](BufferBuilder &builder) { builder.add_uid(offset); }; + }); + return propertyMapper; +} diff --git a/common/domain/mail.fbs b/common/domain/mail.fbs new file mode 100644 index 0000000..654f49c --- /dev/null +++ b/common/domain/mail.fbs @@ -0,0 +1,14 @@ +namespace Akonadi2.ApplicationDomain.Buffer; + +table Mail { + uid:string; + sender:string; + senderName:string; + subject:string; + date:string; + unread:bool = false; + important:bool = false; +} + +root_type Mail; +file_identifier "AKFB"; diff --git a/common/domain/mail.h b/common/domain/mail.h new file mode 100644 index 0000000..b58ce44 --- /dev/null +++ b/common/domain/mail.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +#include "applicationdomaintype.h" + +#include "storage.h" + +class ResultSet; +class QByteArray; + +template +class ReadPropertyMapper; +template +class WritePropertyMapper; + +namespace Akonadi2 { + class Query; + +namespace ApplicationDomain { + namespace Buffer { + struct Mail; + struct MailBuilder; + } + +template<> +class TypeImplementation { +public: + typedef Akonadi2::ApplicationDomain::Buffer::Mail Buffer; + typedef Akonadi2::ApplicationDomain::Buffer::MailBuilder BufferBuilder; + static QSet indexedProperties(); + /** + * Returns the potential result set based on the indexes. + * + * An empty result set indicates that a full scan is required. + */ + static ResultSet queryIndexes(const Akonadi2::Query &query, const QByteArray &resourceInstanceIdentifier, QSet &appliedFilters, Akonadi2::Storage::Transaction &transaction); + static void index(const Mail &type, Akonadi2::Storage::Transaction &transaction); + static QSharedPointer > initializeReadPropertyMapper(); + static QSharedPointer > initializeWritePropertyMapper(); +}; + +} +} diff --git a/common/domainadaptor.h b/common/domainadaptor.h index f9dcc79..4943cc0 100644 --- a/common/domainadaptor.h +++ b/common/domainadaptor.h @@ -25,6 +25,7 @@ #include "domain/applicationdomaintype.h" #include "domain/event.h" +#include "domain/mail.h" #include "entity_generated.h" #include "metadata_generated.h" #include "entitybuffer.h" diff --git a/common/entitystorage.cpp b/common/entitystorage.cpp index 8a3391e..bcc3562 100644 --- a/common/entitystorage.cpp +++ b/common/entitystorage.cpp @@ -19,9 +19,9 @@ #include "entitystorage.h" -static void scan(const Akonadi2::Storage::Transaction &transaction, const QByteArray &key, std::function callback) +static void scan(const Akonadi2::Storage::Transaction &transaction, const QByteArray &key, std::function callback, const QByteArray &bufferType) { - transaction.openDatabase().scan(key, [=](const QByteArray &key, const QByteArray &value) -> bool { + transaction.openDatabase(bufferType + ".main").scan(key, [=](const QByteArray &key, const QByteArray &value) -> bool { //Skip internals if (Akonadi2::Storage::isInternalKey(key)) { return true; @@ -58,17 +58,17 @@ void EntityStorageBase::readValue(const Akonadi2::Storage::Transaction &transact auto domainObject = create(key, revision, mDomainTypeAdaptorFactory->createAdaptor(entity)); resultCallback(domainObject); return true; - }); + }, mBufferType); } -static ResultSet fullScan(const Akonadi2::Storage::Transaction &transaction) +static ResultSet fullScan(const Akonadi2::Storage::Transaction &transaction, const QByteArray &bufferType) { //TODO use a result set with an iterator, to read values on demand QVector keys; scan(transaction, QByteArray(), [=, &keys](const QByteArray &key, const Akonadi2::Entity &) { keys << key; return true; - }); + }, bufferType); Trace() << "Full scan found " << keys.size() << " results"; return ResultSet(keys); } @@ -99,7 +99,7 @@ ResultSet EntityStorageBase::getResultSet(const Akonadi2::Query &query, Akonadi2 //We do a full scan if there were no indexes available to create the initial set. if (appliedFilters.isEmpty()) { - resultSet = fullScan(transaction); + resultSet = fullScan(transaction, mBufferType); } auto filter = [remainingFilters, query, baseRevision, topRevision](const Akonadi2::ApplicationDomain::ApplicationDomainType::Ptr &domainObject) -> bool { diff --git a/common/entitystorage.h b/common/entitystorage.h index 8256938..9d928b8 100644 --- a/common/entitystorage.h +++ b/common/entitystorage.h @@ -52,6 +52,7 @@ protected: protected: QByteArray mResourceInstanceIdentifier; + QByteArray mBufferType; DomainTypeAdaptorFactoryInterface::Ptr mDomainTypeAdaptorFactory; }; @@ -60,10 +61,10 @@ class EntityStorage : public EntityStorageBase { public: - EntityStorage(const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory) + EntityStorage(const QByteArray &instanceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory, const QByteArray &bufferType) : EntityStorageBase(instanceIdentifier, adaptorFactory) { - + mBufferType = bufferType; } protected: @@ -84,7 +85,7 @@ protected: public: - virtual void read(const Akonadi2::Query &query, const QPair &revisionRange, const QSharedPointer > &resultProvider) + virtual void read(const Akonadi2::Query &query, const QPair &revisionRange, const QSharedPointer > &resultProvider) { Akonadi2::Storage storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier); storage.setDefaultErrorHandler([](const Akonadi2::Storage::Error &error) { diff --git a/common/facade.h b/common/facade.h index be053f6..d53ec4a 100644 --- a/common/facade.h +++ b/common/facade.h @@ -109,7 +109,7 @@ public: GenericFacade(const QByteArray &resourceIdentifier, const DomainTypeAdaptorFactoryInterface::Ptr &adaptorFactory = DomainTypeAdaptorFactoryInterface::Ptr(), const QSharedPointer > storage = QSharedPointer >(), const QSharedPointer resourceAccess = QSharedPointer()) : Akonadi2::StoreFacade(), mResourceAccess(resourceAccess), - mStorage(storage ? storage : QSharedPointer >::create(resourceIdentifier, adaptorFactory)), + mStorage(storage ? storage : QSharedPointer >::create(resourceIdentifier, adaptorFactory, bufferTypeForDomainType())), mDomainTypeAdaptorFactory(adaptorFactory), mResourceInstanceIdentifier(resourceIdentifier) { diff --git a/common/pipeline.cpp b/common/pipeline.cpp index 8ef6187..33e5d5c 100644 --- a/common/pipeline.cpp +++ b/common/pipeline.cpp @@ -143,7 +143,7 @@ KAsync::Job Pipeline::newEntity(void const *command, size_t size) auto createEntity = Akonadi2::Commands::GetCreateEntity(command); //TODO rename createEntitiy->domainType to bufferType - const QString entityType = QString::fromUtf8(reinterpret_cast(createEntity->domainType()->Data()), createEntity->domainType()->size()); + const QByteArray bufferType = QByteArray(reinterpret_cast(createEntity->domainType()->Data()), createEntity->domainType()->size()); { flatbuffers::Verifier verifyer(reinterpret_cast(createEntity->delta()->Data()), createEntity->delta()->size()); if (!Akonadi2::VerifyEntityBuffer(verifyer)) { @@ -152,6 +152,10 @@ KAsync::Job Pipeline::newEntity(void const *command, size_t size) } } auto entity = Akonadi2::GetEntity(createEntity->delta()->Data()); + if (!entity->resource()->size() && !entity->local()->size()) { + Warning() << "No local and no resource buffer while trying to create entity."; + return KAsync::error(); + } //Add metadata buffer flatbuffers::FlatBufferBuilder metadataFbb; @@ -165,14 +169,14 @@ KAsync::Job Pipeline::newEntity(void const *command, size_t size) flatbuffers::FlatBufferBuilder fbb; EntityBuffer::assembleEntityBuffer(fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), entity->resource()->Data(), entity->resource()->size(), entity->local()->Data(), entity->local()->size()); - d->transaction.openDatabase().write(key, QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize())); + d->transaction.openDatabase(bufferType + ".main").write(key, QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize())); Akonadi2::Storage::setMaxRevision(d->transaction, newRevision); - Log() << "Pipeline: wrote entity: " << key << newRevision; + Log() << "Pipeline: wrote entity: " << key << newRevision << bufferType; - return KAsync::start([this, key, entityType, newRevision](KAsync::Future &future) { - PipelineState state(this, NewPipeline, key, d->newPipeline[entityType], newRevision, [&future]() { + return KAsync::start([this, key, bufferType, newRevision](KAsync::Future &future) { + PipelineState state(this, NewPipeline, key, d->newPipeline[bufferType], newRevision, [&future]() { future.setFinished(); - }); + }, bufferType); d->activePipelines << state; state.step(); }); @@ -195,10 +199,10 @@ KAsync::Job Pipeline::modifiedEntity(void const *command, size_t size) Q_ASSERT(modifyEntity); //TODO rename modifyEntity->domainType to bufferType - const QByteArray entityType = QByteArray(reinterpret_cast(modifyEntity->domainType()->Data()), modifyEntity->domainType()->size()); + const QByteArray bufferType = QByteArray(reinterpret_cast(modifyEntity->domainType()->Data()), modifyEntity->domainType()->size()); const QByteArray key = QByteArray(reinterpret_cast(modifyEntity->entityId()->Data()), modifyEntity->entityId()->size()); - if (entityType.isEmpty() || key.isEmpty()) { - Warning() << "entity type or key " << entityType << key; + if (bufferType.isEmpty() || key.isEmpty()) { + Warning() << "entity type or key " << bufferType << key; return KAsync::error(); } { @@ -209,9 +213,9 @@ KAsync::Job Pipeline::modifiedEntity(void const *command, size_t size) } } - auto adaptorFactory = d->adaptorFactory.value(entityType); + auto adaptorFactory = d->adaptorFactory.value(bufferType); if (!adaptorFactory) { - Warning() << "no adaptor factory for type " << entityType; + Warning() << "no adaptor factory for type " << bufferType; return KAsync::error(); } @@ -220,7 +224,7 @@ KAsync::Job Pipeline::modifiedEntity(void const *command, size_t size) auto diff = adaptorFactory->createAdaptor(*diffEntity); QSharedPointer current; - storage().createTransaction(Akonadi2::Storage::ReadOnly).openDatabase().scan(key, [¤t, adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { + storage().createTransaction(Akonadi2::Storage::ReadOnly).openDatabase(bufferType + ".main").scan(key, [¤t, adaptorFactory](const QByteArray &key, const QByteArray &data) -> bool { Akonadi2::EntityBuffer buffer(const_cast(data.data()), data.size()); if (!buffer.isValid()) { Warning() << "Read invalid buffer from disk"; @@ -228,6 +232,9 @@ KAsync::Job Pipeline::modifiedEntity(void const *command, size_t size) current = adaptorFactory->createAdaptor(buffer.entity()); } return false; + }, + [](const Storage::Error &error) { + Warning() << "Failed to read value from storage: " << error.message; }); //TODO error handler @@ -265,13 +272,13 @@ KAsync::Job Pipeline::modifiedEntity(void const *command, size_t size) adaptorFactory->createBuffer(*newObject, fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize()); //TODO don't overwrite the old entry, but instead store a new revision - d->transaction.openDatabase().write(key, QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize())); + d->transaction.openDatabase(bufferType + ".main").write(key, QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize())); Akonadi2::Storage::setMaxRevision(d->transaction, newRevision); - return KAsync::start([this, key, entityType, newRevision](KAsync::Future &future) { - PipelineState state(this, ModifiedPipeline, key, d->modifiedPipeline[entityType], newRevision, [&future]() { + return KAsync::start([this, key, bufferType, newRevision](KAsync::Future &future) { + PipelineState state(this, ModifiedPipeline, key, d->modifiedPipeline[bufferType], newRevision, [&future]() { future.setFinished(); - }); + }, bufferType); d->activePipelines << state; state.step(); }); @@ -292,18 +299,18 @@ KAsync::Job Pipeline::deletedEntity(void const *command, size_t size) } auto deleteEntity = Akonadi2::Commands::GetDeleteEntity(command); - const QByteArray entityType = QByteArray(reinterpret_cast(deleteEntity->domainType()->Data()), deleteEntity->domainType()->size()); + const QByteArray bufferType = QByteArray(reinterpret_cast(deleteEntity->domainType()->Data()), deleteEntity->domainType()->size()); const QByteArray key = QByteArray(reinterpret_cast(deleteEntity->entityId()->Data()), deleteEntity->entityId()->size()); //TODO instead of deleting the entry, a new revision should be created that marks the entity as deleted - d->transaction.openDatabase().remove(key); + d->transaction.openDatabase(bufferType + ".main").remove(key); Akonadi2::Storage::setMaxRevision(d->transaction, newRevision); Log() << "Pipeline: deleted entity: "<< newRevision; - return KAsync::start([this, key, entityType, newRevision](KAsync::Future &future) { - PipelineState state(this, DeletedPipeline, key, d->deletedPipeline[entityType], newRevision, [&future](){ + return KAsync::start([this, key, bufferType, newRevision](KAsync::Future &future) { + PipelineState state(this, DeletedPipeline, key, d->deletedPipeline[bufferType], newRevision, [&future](){ future.setFinished(); - }); + }, bufferType); d->activePipelines << state; state.step(); }); @@ -354,14 +361,15 @@ void Pipeline::pipelineCompleted(PipelineState state) class PipelineState::Private : public QSharedData { public: - Private(Pipeline *p, Pipeline::Type t, const QByteArray &k, QVector filters, const std::function &c, qint64 r) + Private(Pipeline *p, Pipeline::Type t, const QByteArray &k, QVector filters, const std::function &c, qint64 r, const QByteArray &b) : pipeline(p), type(t), key(k), filterIt(filters), idle(true), callback(c), - revision(r) + revision(r), + bufferType(b) {} Private() @@ -378,6 +386,7 @@ public: bool idle; std::function callback; qint64 revision; + QByteArray bufferType; }; PipelineState::PipelineState() @@ -386,8 +395,8 @@ PipelineState::PipelineState() } -PipelineState::PipelineState(Pipeline *pipeline, Pipeline::Type type, const QByteArray &key, const QVector &filters, qint64 revision, const std::function &callback) - : d(new Private(pipeline, type, key, filters, callback, revision)) +PipelineState::PipelineState(Pipeline *pipeline, Pipeline::Type type, const QByteArray &key, const QVector &filters, qint64 revision, const std::function &callback, const QByteArray &bufferType) + : d(new Private(pipeline, type, key, filters, callback, revision, bufferType)) { } @@ -431,6 +440,11 @@ qint64 PipelineState::revision() const return d->revision; } +QByteArray PipelineState::bufferType() const +{ + return d->bufferType; +} + void PipelineState::step() { if (!d->pipeline) { diff --git a/common/pipeline.h b/common/pipeline.h index a3b3735..573af73 100644 --- a/common/pipeline.h +++ b/common/pipeline.h @@ -85,7 +85,7 @@ class AKONADI2COMMON_EXPORT PipelineState { public: PipelineState(); - PipelineState(Pipeline *pipeline, Pipeline::Type type, const QByteArray &key, const QVector &filters, qint64 revision, const std::function &callback); + PipelineState(Pipeline *pipeline, Pipeline::Type type, const QByteArray &key, const QVector &filters, qint64 revision, const std::function &callback, const QByteArray &bufferType); PipelineState(const PipelineState &other); ~PipelineState(); @@ -96,7 +96,7 @@ public: QByteArray key() const; Pipeline::Type type() const; qint64 revision() const; - //TODO expose command + QByteArray bufferType() const; void step(); void processingCompleted(Preprocessor *filter); @@ -114,7 +114,6 @@ public: Preprocessor(); virtual ~Preprocessor(); - //TODO pass actual command as well, for changerecording virtual void process(const PipelineState &state, Akonadi2::Storage::Transaction &transaction); //TODO to record progress virtual QString id() const; @@ -142,7 +141,7 @@ public: void process(const PipelineState &state, Akonadi2::Storage::Transaction &transaction) Q_DECL_OVERRIDE { - transaction.openDatabase().scan(state.key(), [this, &state, &transaction](const QByteArray &key, const QByteArray &value) -> bool { + transaction.openDatabase(state.bufferType() + ".main").scan(state.key(), [this, &state, &transaction](const QByteArray &key, const QByteArray &value) -> bool { auto entity = Akonadi2::GetEntity(value); mFunction(state, *entity, transaction); processingCompleted(state); diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp index 89495ae..7ff072a 100644 --- a/common/propertymapper.cpp +++ b/common/propertymapper.cpp @@ -38,3 +38,9 @@ QVariant propertyToVariant(const flatbuffers::String *property) return QVariant(); } +template <> +QVariant propertyToVariant(uint8_t property) +{ + return static_cast(property); +} + diff --git a/common/propertymapper.h b/common/propertymapper.h index 0c6c16f..72468e2 100644 --- a/common/propertymapper.h +++ b/common/propertymapper.h @@ -35,6 +35,8 @@ flatbuffers::uoffset_t variantToProperty(const QVariant &, flatbuffers::FlatBuff */ template QVariant propertyToVariant(const flatbuffers::String *); +template +QVariant propertyToVariant(uint8_t); /** diff --git a/examples/dummyresource/domainadaptor.cpp b/examples/dummyresource/domainadaptor.cpp index 4cc592e..d08a783 100644 --- a/examples/dummyresource/domainadaptor.cpp +++ b/examples/dummyresource/domainadaptor.cpp @@ -45,3 +45,9 @@ DummyEventAdaptorFactory::DummyEventAdaptorFactory() }); } +DummyMailAdaptorFactory::DummyMailAdaptorFactory() + : DomainTypeAdaptorFactory() +{ + +} + diff --git a/examples/dummyresource/domainadaptor.h b/examples/dummyresource/domainadaptor.h index 8b6d96b..add3e8e 100644 --- a/examples/dummyresource/domainadaptor.h +++ b/examples/dummyresource/domainadaptor.h @@ -20,6 +20,7 @@ #include "common/domainadaptor.h" #include "event_generated.h" +#include "mail_generated.h" #include "dummycalendar_generated.h" #include "entity_generated.h" @@ -29,3 +30,11 @@ public: DummyEventAdaptorFactory(); virtual ~DummyEventAdaptorFactory() {}; }; + +//TODO replace the resource specific event class by a mail class or a dummy class if no resource type is required. +class DummyMailAdaptorFactory : public DomainTypeAdaptorFactory +{ +public: + DummyMailAdaptorFactory(); + virtual ~DummyMailAdaptorFactory() {}; +}; diff --git a/examples/dummyresource/dummystore.cpp b/examples/dummyresource/dummystore.cpp index 5a3f74b..41b48ed 100644 --- a/examples/dummyresource/dummystore.cpp +++ b/examples/dummyresource/dummystore.cpp @@ -21,6 +21,7 @@ #include #include "dummycalendar_generated.h" +#include "mail_generated.h" static std::string createEvent(int i) { @@ -43,6 +44,20 @@ static std::string createEvent(int i) return std::string(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); } +static std::string createMail(int i) +{ + static flatbuffers::FlatBufferBuilder fbb; + fbb.Clear(); + { + auto subject = fbb.CreateString("summary" + std::to_string(i)); + Akonadi2::ApplicationDomain::Buffer::MailBuilder mailBuilder(fbb); + mailBuilder.add_subject(subject); + Akonadi2::ApplicationDomain::Buffer::FinishMailBuffer(fbb, mailBuilder.Finish()); + } + + return std::string(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); +} + QMap populate() { QMap content; @@ -53,10 +68,24 @@ QMap populate() return content; } -static QMap s_dataSource = populate(); +QMap populateMails() +{ + QMap content; + for (int i = 0; i < 2; i++) { + content.insert(QString("key%1").arg(i), QString::fromStdString(createMail(i))); + } + return content; +} +static QMap s_dataSource = populate(); +static QMap s_mailSource = populateMails(); -QMap DummyStore::data() const +QMap DummyStore::events() const { return s_dataSource; } + +QMap DummyStore::mails() const +{ + return s_mailSource; +} diff --git a/examples/dummyresource/dummystore.h b/examples/dummyresource/dummystore.h index 6a404ae..ba1c0ae 100644 --- a/examples/dummyresource/dummystore.h +++ b/examples/dummyresource/dummystore.h @@ -29,6 +29,6 @@ public: return instance; } - QMap data() const; - + QMap events() const; + QMap mails() const; }; diff --git a/examples/dummyresource/facade.cpp b/examples/dummyresource/facade.cpp index d20d12d..63f84f2 100644 --- a/examples/dummyresource/facade.cpp +++ b/examples/dummyresource/facade.cpp @@ -30,3 +30,11 @@ DummyResourceFacade::~DummyResourceFacade() { } +DummyResourceMailFacade::DummyResourceMailFacade(const QByteArray &instanceIdentifier) + : Akonadi2::GenericFacade(instanceIdentifier, QSharedPointer::create()) +{ +} + +DummyResourceMailFacade::~DummyResourceMailFacade() +{ +} diff --git a/examples/dummyresource/facade.h b/examples/dummyresource/facade.h index dde0dc2..87f68c3 100644 --- a/examples/dummyresource/facade.h +++ b/examples/dummyresource/facade.h @@ -28,3 +28,10 @@ public: DummyResourceFacade(const QByteArray &instanceIdentifier); virtual ~DummyResourceFacade(); }; + +class DummyResourceMailFacade : public Akonadi2::GenericFacade +{ +public: + DummyResourceMailFacade(const QByteArray &instanceIdentifier); + virtual ~DummyResourceMailFacade(); +}; diff --git a/examples/dummyresource/resourcefactory.cpp b/examples/dummyresource/resourcefactory.cpp index 0e18282..8d605b9 100644 --- a/examples/dummyresource/resourcefactory.cpp +++ b/examples/dummyresource/resourcefactory.cpp @@ -23,6 +23,7 @@ #include "entitybuffer.h" #include "pipeline.h" #include "dummycalendar_generated.h" +#include "mail_generated.h" #include "queuedcommand_generated.h" #include "createentity_generated.h" #include "domainadaptor.h" @@ -30,21 +31,23 @@ #include "index.h" #include "log.h" #include "domain/event.h" +#include "domain/mail.h" #include "dummystore.h" #include "definitions.h" #include "facadefactory.h" //This is the resources entity type, and not the domain type #define ENTITY_TYPE_EVENT "event" +#define ENTITY_TYPE_MAIL "mail" DummyResource::DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer &pipeline) : Akonadi2::GenericResource(instanceIdentifier, pipeline) { auto eventFactory = QSharedPointer::create(); const auto resourceIdentifier = mResourceInstanceIdentifier; + auto eventIndexer = new Akonadi2::SimpleProcessor("eventIndexer", [eventFactory, resourceIdentifier](const Akonadi2::PipelineState &state, const Akonadi2::Entity &entity, Akonadi2::Storage::Transaction &transaction) { auto adaptor = eventFactory->createAdaptor(entity); - //FIXME set revision? Akonadi2::ApplicationDomain::Event event(resourceIdentifier, state.key(), -1, adaptor); Akonadi2::ApplicationDomain::TypeImplementation::index(event, transaction); @@ -58,15 +61,75 @@ DummyResource::DummyResource(const QByteArray &instanceIdentifier, const QShared mPipeline->setPreprocessors(ENTITY_TYPE_EVENT, Akonadi2::Pipeline::NewPipeline, QVector() << eventIndexer); mPipeline->setAdaptorFactory(ENTITY_TYPE_EVENT, eventFactory); //TODO cleanup indexes during removal + + { + auto mailFactory = QSharedPointer::create(); + auto mailIndexer = new Akonadi2::SimpleProcessor("mailIndexer", [mailFactory, resourceIdentifier](const Akonadi2::PipelineState &state, const Akonadi2::Entity &entity, Akonadi2::Storage::Transaction &transaction) { + auto adaptor = mailFactory->createAdaptor(entity); + Akonadi2::ApplicationDomain::Mail mail(resourceIdentifier, state.key(), -1, adaptor); + Akonadi2::ApplicationDomain::TypeImplementation::index(mail, transaction); + + Index ridIndex("mail.index.rid", transaction); + const auto rid = mail.getProperty("remoteId"); + if (rid.isValid()) { + ridIndex.add(rid.toByteArray(), mail.identifier()); + } + }); + + mPipeline->setPreprocessors(ENTITY_TYPE_MAIL, Akonadi2::Pipeline::NewPipeline, QVector() << mailIndexer); + mPipeline->setAdaptorFactory(ENTITY_TYPE_MAIL, mailFactory); + } +} + +void DummyResource::createEvent(const QByteArray &ridBuffer, const QByteArray &data, flatbuffers::FlatBufferBuilder &entityFbb) +{ + auto eventBuffer = DummyCalendar::GetDummyEvent(data.data()); + + //Map the source format to the buffer format (which happens to be an exact copy here) + auto summary = m_fbb.CreateString(eventBuffer->summary()->c_str()); + auto rid = m_fbb.CreateString(std::string(ridBuffer.constData(), ridBuffer.size())); + auto description = m_fbb.CreateString(std::string(ridBuffer.constData(), ridBuffer.size())); + static uint8_t rawData[100]; + auto attachment = Akonadi2::EntityBuffer::appendAsVector(m_fbb, rawData, 100); + + auto builder = DummyCalendar::DummyEventBuilder(m_fbb); + builder.add_summary(summary); + builder.add_remoteId(rid); + builder.add_description(description); + builder.add_attachment(attachment); + auto buffer = builder.Finish(); + DummyCalendar::FinishDummyEventBuffer(m_fbb, buffer); + Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize(), 0, 0); +} + +void DummyResource::createMail(const QByteArray &ridBuffer, const QByteArray &data, flatbuffers::FlatBufferBuilder &entityFbb) +{ + auto mailBuffer = Akonadi2::ApplicationDomain::Buffer::GetMail(data.data()); + + //Map the source format to the buffer format (which happens to be an exact copy here) + auto subject = m_fbb.CreateString(mailBuffer->subject()->c_str()); + auto rid = m_fbb.CreateString(std::string(ridBuffer.constData(), ridBuffer.size())); + // auto description = m_fbb.CreateString(std::string(ridBuffer.constData(), ridBuffer.size())); + // static uint8_t rawData[100]; + // auto attachment = Akonadi2::EntityBuffer::appendAsVector(m_fbb, rawData, 100); + + auto builder = Akonadi2::ApplicationDomain::Buffer::MailBuilder(m_fbb); + builder.add_subject(subject); + // builder.add(rid); + // builder.add_description(description); + // builder.add_attachment(attachment); + auto buffer = builder.Finish(); + Akonadi2::ApplicationDomain::Buffer::FinishMailBuffer(m_fbb, buffer); + Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize()); } KAsync::Job DummyResource::synchronizeWithSource() { return KAsync::start([this](KAsync::Future &f) { - auto transaction = Akonadi2::Storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier + ".index.uid", Akonadi2::Storage::ReadOnly).createTransaction(Akonadi2::Storage::ReadOnly); + auto transaction = Akonadi2::Storage(Akonadi2::storageLocation(), mResourceInstanceIdentifier, Akonadi2::Storage::ReadOnly).createTransaction(Akonadi2::Storage::ReadOnly); Index uidIndex("index.uid", transaction); - const auto data = DummyStore::instance().data(); + const auto data = DummyStore::instance().events(); for (auto it = data.constBegin(); it != data.constEnd(); it++) { bool isNew = true; uidIndex.lookup(it.key().toLatin1(), [&](const QByteArray &value) { @@ -80,25 +143,8 @@ KAsync::Job DummyResource::synchronizeWithSource() if (isNew) { m_fbb.Clear(); - const QByteArray data = it.value().toUtf8(); - auto eventBuffer = DummyCalendar::GetDummyEvent(data.data()); - - //Map the source format to the buffer format (which happens to be an exact copy here) - auto summary = m_fbb.CreateString(eventBuffer->summary()->c_str()); - auto rid = m_fbb.CreateString(it.key().toStdString().c_str()); - auto description = m_fbb.CreateString(it.key().toStdString().c_str()); - static uint8_t rawData[100]; - auto attachment = Akonadi2::EntityBuffer::appendAsVector(m_fbb, rawData, 100); - - auto builder = DummyCalendar::DummyEventBuilder(m_fbb); - builder.add_summary(summary); - builder.add_remoteId(rid); - builder.add_description(description); - builder.add_attachment(attachment); - auto buffer = builder.Finish(); - DummyCalendar::FinishDummyEventBuffer(m_fbb, buffer); flatbuffers::FlatBufferBuilder entityFbb; - Akonadi2::EntityBuffer::assembleEntityBuffer(entityFbb, 0, 0, m_fbb.GetBufferPointer(), m_fbb.GetSize(), 0, 0); + createEvent(it.key().toUtf8(), it.value().toUtf8(), entityFbb); flatbuffers::FlatBufferBuilder fbb; //This is the resource type and not the domain type @@ -113,6 +159,43 @@ KAsync::Job DummyResource::synchronizeWithSource() } } //TODO find items to remove + + const auto mails = DummyStore::instance().mails(); + for (auto it = mails.constBegin(); it != mails.constEnd(); it++) { + bool isNew = true; + uidIndex.lookup(it.key().toLatin1(), [&](const QByteArray &value) { + isNew = false; + }, + [](const Index::Error &error) { + if (error.code != Index::IndexNotAvailable) { + Warning() << "Error in uid index: " << error.message; + } + }); + if (isNew) { + m_fbb.Clear(); + + flatbuffers::FlatBufferBuilder entityFbb; + createMail(it.key().toUtf8(), it.value().toUtf8(), entityFbb); + + flatbuffers::Verifier verifyer(reinterpret_cast(entityFbb.GetBufferPointer()), entityFbb.GetSize()); + if (!Akonadi2::ApplicationDomain::Buffer::VerifyMailBuffer(verifyer)) { + Warning() << "invalid buffer, not a mail buffer"; + } + + flatbuffers::FlatBufferBuilder fbb; + //This is the resource type and not the domain type + auto type = fbb.CreateString(ENTITY_TYPE_MAIL); + auto delta = Akonadi2::EntityBuffer::appendAsVector(fbb, entityFbb.GetBufferPointer(), entityFbb.GetSize()); + auto location = Akonadi2::Commands::CreateCreateEntity(fbb, type, delta); + Akonadi2::Commands::FinishCreateEntityBuffer(fbb, location); + + enqueueCommand(mSynchronizerQueue, Akonadi2::Commands::CreateEntityCommand, QByteArray::fromRawData(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize())); + } else { //modification + //TODO diff and create modification if necessary + } + } + //TODO find items to remove + f.setFinished(); }); } @@ -132,6 +215,7 @@ Akonadi2::Resource *DummyResourceFactory::createResource(const QByteArray &insta void DummyResourceFactory::registerFacades(Akonadi2::FacadeFactory &factory) { factory.registerFacade(PLUGIN_NAME); + factory.registerFacade(PLUGIN_NAME); } #include "resourcefactory.moc" diff --git a/examples/dummyresource/resourcefactory.h b/examples/dummyresource/resourcefactory.h index 4baafa7..cf0f624 100644 --- a/examples/dummyresource/resourcefactory.h +++ b/examples/dummyresource/resourcefactory.h @@ -34,6 +34,9 @@ class DummyResource : public Akonadi2::GenericResource public: DummyResource(const QByteArray &instanceIdentifier, const QSharedPointer &pipeline = QSharedPointer()); KAsync::Job synchronizeWithSource() Q_DECL_OVERRIDE; +private: + void createEvent(const QByteArray &rid, const QByteArray &data, flatbuffers::FlatBufferBuilder &entityFbb); + void createMail(const QByteArray &rid, const QByteArray &data, flatbuffers::FlatBufferBuilder &entityFbb); }; class DummyResourceFactory : public Akonadi2::ResourceFactory diff --git a/tests/dummyresourcetest.cpp b/tests/dummyresourcetest.cpp index e3b3f07..a28e071 100644 --- a/tests/dummyresourcetest.cpp +++ b/tests/dummyresourcetest.cpp @@ -164,6 +164,21 @@ private Q_SLOTS: qDebug() << value->getProperty("summary").toString(); } + void testSyncAndFacadeMail() + { + Akonadi2::Query query; + query.resources << "org.kde.dummy.instance1"; + query.syncOnDemand = true; + query.processAll = true; + + async::SyncListResult result(Akonadi2::Store::load(query)); + result.exec(); + QVERIFY(!result.isEmpty()); + auto value = result.first(); + QVERIFY(!value->getProperty("subject").toString().isEmpty()); + qDebug() << value->getProperty("subject").toString(); + } + void testWriteModifyDelete() { Akonadi2::ApplicationDomain::Event event; diff --git a/tests/genericfacadebenchmark.cpp b/tests/genericfacadebenchmark.cpp index 483a597..7cd6c75 100644 --- a/tests/genericfacadebenchmark.cpp +++ b/tests/genericfacadebenchmark.cpp @@ -88,7 +88,7 @@ private Q_SLOTS: QBENCHMARK { auto resultSet = QSharedPointer >::create(); auto resourceAccess = QSharedPointer::create(); - auto storage = QSharedPointer >::create("identifier", domainTypeAdaptorFactory); + auto storage = QSharedPointer >::create("identifier", domainTypeAdaptorFactory, "bufferType"); TestResourceFacade facade(identifier, storage, resourceAccess); async::SyncListResult result(resultSet->emitter()); diff --git a/tests/genericfacadetest.cpp b/tests/genericfacadetest.cpp index 7aaec23..45ca54d 100644 --- a/tests/genericfacadetest.cpp +++ b/tests/genericfacadetest.cpp @@ -74,7 +74,7 @@ private Q_SLOTS: query.liveQuery = false; auto resultSet = QSharedPointer >::create(); - auto storage = QSharedPointer::create("identifier", QSharedPointer::create()); + auto storage = QSharedPointer::create("identifier", QSharedPointer::create(), "bufferType"); auto resourceAccess = QSharedPointer::create(); storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create(); TestResourceFacade facade("identifier", storage, resourceAccess); @@ -96,7 +96,7 @@ private Q_SLOTS: query.liveQuery = true; auto resultSet = QSharedPointer >::create(); - auto storage = QSharedPointer::create("identifier", QSharedPointer::create()); + auto storage = QSharedPointer::create("identifier", QSharedPointer::create(), "bufferType"); auto resourceAccess = QSharedPointer::create(); storage->mResults << Akonadi2::ApplicationDomain::Event::Ptr::create(); TestResourceFacade facade("identifier", storage, resourceAccess); -- cgit v1.2.3