From 319a303bdceba18d0e5629f3de7a2b85646223be Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Tue, 6 Dec 2016 18:32:32 +0100 Subject: Wrap blob properties in type so we can distinguish it from other properties. When moving an entity to another resource we have to move the blob properties to a temporary directory first, and that requires that we are able to distinguish blob properties from the rest at runtime. --- common/domain/applicationdomaintype.cpp | 26 ++++++++++++--- common/domain/applicationdomaintype.h | 30 +++++++++++++++--- common/propertymapper.cpp | 19 +++++++++++ examples/maildirresource/maildirresource.cpp | 46 ++++++++++++++------------- tests/domainadaptortest.cpp | 47 ++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 30 deletions(-) diff --git a/common/domain/applicationdomaintype.cpp b/common/domain/applicationdomaintype.cpp index 105ae56..1e54622 100644 --- a/common/domain/applicationdomaintype.cpp +++ b/common/domain/applicationdomaintype.cpp @@ -31,6 +31,25 @@ namespace ApplicationDomain { constexpr const char *Mail::ThreadId::name; +void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::ApplicationDomain::BufferAdaptor &memoryAdaptor, const QList &properties, bool copyBlobs) +{ + auto propertiesToCopy = properties; + if (properties.isEmpty()) { + propertiesToCopy = buffer.availableProperties(); + } + for (const auto &property : propertiesToCopy) { + const auto value = buffer.getProperty(property); + if (copyBlobs && value.canConvert()) { + auto oldPath = value.value().value; + auto newPath = oldPath + "copy"; + QFile::copy(oldPath, newPath); + memoryAdaptor.setProperty(property, QVariant::fromValue(BLOB{newPath})); + } else { + memoryAdaptor.setProperty(property, value); + } + } +} + ApplicationDomainType::ApplicationDomainType() :mAdaptor(new MemoryBufferAdaptor()) { @@ -85,9 +104,6 @@ bool ApplicationDomainType::hasProperty(const QByteArray &key) const QVariant ApplicationDomainType::getProperty(const QByteArray &key) const { Q_ASSERT(mAdaptor); - if (!mAdaptor->availableProperties().contains(key)) { - return QVariant(); - } return mAdaptor->getProperty(key); } @@ -111,7 +127,7 @@ void ApplicationDomainType::setProperty(const QByteArray &key, const Application QByteArray ApplicationDomainType::getBlobProperty(const QByteArray &key) const { - const auto path = getProperty(key).toByteArray(); + const auto path = getProperty(key).value().value; QFile file(path); if (!file.open(QIODevice::ReadOnly)) { SinkError() << "Failed to open the file: " << file.errorString() << path; @@ -131,7 +147,7 @@ void ApplicationDomainType::setBlobProperty(const QByteArray &key, const QByteAr file.write(value); //Ensure that the file is written to disk immediately file.close(); - setProperty(key, path); + setProperty(key, QVariant::fromValue(BLOB{path})); } void ApplicationDomainType::setChangedProperties(const QSet &changeset) diff --git a/common/domain/applicationdomaintype.h b/common/domain/applicationdomaintype.h index d6bbdd4..621a512 100644 --- a/common/domain/applicationdomaintype.h +++ b/common/domain/applicationdomaintype.h @@ -63,12 +63,12 @@ #define SINK_BLOB_PROPERTY(NAME, LOWERCASENAME) \ struct NAME { \ static constexpr const char *name = #LOWERCASENAME; \ - typedef QString Type; \ + typedef BLOB Type; \ }; \ void set##NAME(const QByteArray &value) { setBlobProperty(NAME::name, value); } \ - void set##NAME##Path(const QString &path) { setProperty(NAME::name, QVariant::fromValue(path)); } \ + void set##NAME##Path(const QString &path) { setProperty(NAME::name, QVariant::fromValue(BLOB{path})); } \ QByteArray get##NAME() const { return getBlobProperty(NAME::name); } \ - QString get##NAME##Path() const { return getProperty(NAME::name).value(); } \ + QString get##NAME##Path() const { return getProperty(NAME::name).value().value; } \ #define SINK_REFERENCE_PROPERTY(TYPE, NAME, LOWERCASENAME) \ struct NAME { \ @@ -98,6 +98,12 @@ struct SINK_EXPORT Progress { }; +struct BLOB { + QString value; +}; + +void copyBuffer(Sink::ApplicationDomain::BufferAdaptor &buffer, Sink::ApplicationDomain::BufferAdaptor &memoryAdaptor, const QList &properties, bool copyBlobs); + /** * The domain type interface has two purposes: * * provide a unified interface to read buffers (for zero-copy reading) @@ -115,14 +121,29 @@ public: ApplicationDomainType(const ApplicationDomainType &other); ApplicationDomainType& operator=(const ApplicationDomainType &other); + /** + * Returns an in memory representation of the same entity. + */ template static typename DomainType::Ptr getInMemoryRepresentation(const ApplicationDomainType &domainType, const QList properties = QList()) { auto memoryAdaptor = QSharedPointer::create(*(domainType.mAdaptor), properties); - //The identifier still internal refers to the memory-mapped pointer, we need to copy the memory or it will become invalid + //mIdentifier internally still refers to the memory-mapped memory, we need to copy the memory or it will become invalid return QSharedPointer::create(domainType.mResourceInstanceIdentifier, QByteArray(domainType.mIdentifier.constData(), domainType.mIdentifier.size()), domainType.mRevision, memoryAdaptor); } + /** + * Returns an in memory copy without id and resource set. + */ + template + static typename DomainType::Ptr getInMemoryCopy(const ApplicationDomainType &domainType, const QList properties = QList()) + { + auto memoryAdaptor = QSharedPointer::create(); + Q_ASSERT(domainType.mAdaptor); + copyBuffer(*(domainType.mAdaptor), *memoryAdaptor, properties, true); + return QSharedPointer::create(QByteArray{}, QByteArray{}, 0, memoryAdaptor); + } + static QByteArray generateUid(); template @@ -430,3 +451,4 @@ Q_DECLARE_METATYPE(Sink::ApplicationDomain::Identity::Ptr) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Mail::Contact) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Error) Q_DECLARE_METATYPE(Sink::ApplicationDomain::Progress) +Q_DECLARE_METATYPE(Sink::ApplicationDomain::BLOB) diff --git a/common/propertymapper.cpp b/common/propertymapper.cpp index 754c874..249221a 100644 --- a/common/propertymapper.cpp +++ b/common/propertymapper.cpp @@ -32,6 +32,15 @@ flatbuffers::uoffset_t variantToProperty(const QVariant &property, flat return 0; } +template <> +flatbuffers::uoffset_t variantToProperty(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) +{ + if (property.isValid()) { + return fbb.CreateString(property.value().value.toStdString()).o; + } + return 0; +} + template <> flatbuffers::uoffset_t variantToProperty(const QVariant &property, flatbuffers::FlatBufferBuilder &fbb) { @@ -110,6 +119,16 @@ QVariant propertyToVariant(const flatbuffers::String *property) return QVariant(); } +template <> +QVariant propertyToVariant(const flatbuffers::String *property) +{ + if (property) { + // We have to copy the memory, otherwise it would become eventually invalid + return QVariant::fromValue(Sink::ApplicationDomain::BLOB{QString::fromStdString(property->c_str())}); + } + return QVariant(); +} + template <> QVariant propertyToVariant(const flatbuffers::String *property) { diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 708dabc..1eee786 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp @@ -129,41 +129,44 @@ public: void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE { - const auto mimeMessage = newEntity.getProperty("mimeMessage"); - if (mimeMessage.isValid()) { - newEntity.setProperty("mimeMessage", moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray())); + const ApplicationDomain::Mail mail{newEntity}; + const auto mimeMessage = mail.getMimeMessagePath(); + if (!mimeMessage.isNull()) { + ApplicationDomain::Mail{newEntity}.setMimeMessagePath(moveMessage(mimeMessage, mail.getFolder())); } } void modifiedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity, Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE { - const auto mimeMessage = newEntity.getProperty("mimeMessage"); - const auto newFolder = newEntity.getProperty("folder"); - const bool mimeMessageChanged = mimeMessage.isValid() && mimeMessage.toString() != oldEntity.getProperty("mimeMessage").toString(); - const bool folderChanged = newFolder.isValid() && newFolder.toString() != oldEntity.getProperty("mimeMessage").toString(); + ApplicationDomain::Mail newMail{newEntity}; + const ApplicationDomain::Mail oldMail{oldEntity}; + const auto mimeMessage = newMail.getMimeMessagePath(); + const auto newFolder = newMail.getFolder(); + const bool mimeMessageChanged = !mimeMessage.isNull() && mimeMessage != oldMail.getMimeMessagePath(); + const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); if (mimeMessageChanged || folderChanged) { SinkTrace() << "Moving mime message: " << mimeMessageChanged << folderChanged; - auto newPath = moveMessage(mimeMessage.toString(), newEntity.getProperty("folder").toByteArray()); - if (newPath != oldEntity.getProperty("mimeMessage").toString()) { - const auto oldPath = getFilePathFromMimeMessagePath(oldEntity.getProperty("mimeMessage").toString()); - newEntity.setProperty("mimeMessage", newPath); + auto newPath = moveMessage(mimeMessage, newMail.getFolder()); + if (newPath != oldMail.getMimeMessagePath()) { + const auto oldPath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); + newMail.setMimeMessagePath(newPath); //Remove the olde mime message if there is a new one QFile::remove(oldPath); } } - auto mimeMessagePath = newEntity.getProperty("mimeMessage").toString(); - const auto maildirPath = getPath(newEntity.getProperty("folder").toByteArray()); + auto mimeMessagePath = newMail.getMimeMessagePath(); + const auto maildirPath = getPath(newMail.getFolder()); KPIM::Maildir maildir(maildirPath, false); const auto file = getFilePathFromMimeMessagePath(mimeMessagePath); QString identifier = KPIM::Maildir::getKeyFromFile(file); //get flags from KPIM::Maildir::Flags flags; - if (!newEntity.getProperty("unread").toBool()) { + if (!newMail.getUnread()) { flags |= KPIM::Maildir::Seen; } - if (newEntity.getProperty("important").toBool()) { + if (newMail.getImportant()) { flags |= KPIM::Maildir::Flagged; } @@ -172,7 +175,8 @@ public: void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE { - const auto filePath = getFilePathFromMimeMessagePath(oldEntity.getProperty("mimeMessage").toString()); + const ApplicationDomain::Mail oldMail{oldEntity}; + const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); QFile::remove(filePath); } QByteArray mResourceInstanceIdentifier; @@ -239,7 +243,7 @@ public: } if (!md.isRoot()) { - folder.setProperty("parent", syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8())); + folder.setParent(syncStore().resolveRemoteId(ENTITY_TYPE_FOLDER, md.parent().path().toUtf8())); } createOrModify(bufferType, remoteId, folder); return remoteId; @@ -317,11 +321,11 @@ public: SinkTrace() << "Found a mail " << filePath << " : " << fileName; Sink::ApplicationDomain::Mail mail; - mail.setProperty("folder", folderLocalId); + mail.setFolder(folderLocalId); //We only store the directory path + key, so we facade can add the changing bits (flags) - mail.setProperty("mimeMessage", KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey); - mail.setProperty("unread", !flags.testFlag(KPIM::Maildir::Seen)); - mail.setProperty("important", flags.testFlag(KPIM::Maildir::Flagged)); + mail.setMimeMessagePath(KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey); + mail.setUnread(!flags.testFlag(KPIM::Maildir::Seen)); + mail.setImportant(flags.testFlag(KPIM::Maildir::Flagged)); createOrModify(bufferType, remoteId, mail); } diff --git a/tests/domainadaptortest.cpp b/tests/domainadaptortest.cpp index c29e61b..4fd04db 100644 --- a/tests/domainadaptortest.cpp +++ b/tests/domainadaptortest.cpp @@ -10,6 +10,7 @@ #include "common/domainadaptor.h" #include "common/entitybuffer.h" #include "event_generated.h" +#include "mail_generated.h" #include "metadata_generated.h" #include "entity_generated.h" @@ -23,6 +24,16 @@ public: } }; +class TestMailFactory : public DomainTypeAdaptorFactory +{ +public: + TestMailFactory() + { + mResourceWriteMapper = QSharedPointer>::create(); + Sink::ApplicationDomain::TypeImplementation::configure(*mResourceWriteMapper); + } +}; + /** * Test of domain adaptor, that it can read and write buffers. */ @@ -90,6 +101,42 @@ private slots: QCOMPARE(adaptor->getProperty("summary").toString(), QString("summary1")); } } + + void testMail() + { + auto writeMapper = QSharedPointer>::create(); + Sink::ApplicationDomain::TypeImplementation::configure(*writeMapper); + + Sink::ApplicationDomain::Mail mail; + mail.setExtractedSubject("summary"); + mail.setMimeMessage("foobar"); + + flatbuffers::FlatBufferBuilder metadataFbb; + auto metadataBuilder = Sink::MetadataBuilder(metadataFbb); + metadataBuilder.add_revision(1); + auto metadataBuffer = metadataBuilder.Finish(); + Sink::FinishMetadataBuffer(metadataFbb, metadataBuffer); + + flatbuffers::FlatBufferBuilder mailFbb; + auto pos = createBufferPart(mail, mailFbb, *writeMapper); + Sink::ApplicationDomain::Buffer::FinishMailBuffer(mailFbb, pos); + + flatbuffers::FlatBufferBuilder fbb; + Sink::EntityBuffer::assembleEntityBuffer( + fbb, metadataFbb.GetBufferPointer(), metadataFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize(), mailFbb.GetBufferPointer(), mailFbb.GetSize()); + + { + std::string data(reinterpret_cast(fbb.GetBufferPointer()), fbb.GetSize()); + Sink::EntityBuffer buffer((void *)(data.data()), data.size()); + + TestMailFactory factory; + auto adaptor = factory.createAdaptor(buffer.entity()); + Sink::ApplicationDomain::Mail readMail{QByteArray{}, QByteArray{}, 0, adaptor}; + QCOMPARE(readMail.getSubject(), mail.getSubject()); + QCOMPARE(readMail.getMimeMessage(), mail.getMimeMessage()); + } + + } }; QTEST_MAIN(DomainAdaptorTest) -- cgit v1.2.3