diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -194,6 +194,68 @@ return mType == other.mType; } +QTextStream &Command::toJson(QTextStream &stream) const +{ + stream << "{\"type\": \""; + +#define case_label(x) case Command::x: stream << #x; break + switch (mType) + { + case_label(Invalid); + + case_label(Hello); + case_label(Login); + case_label(Logout); + + case_label(Transaction); + + case_label(CreateItem); + case_label(CopyItems); + case_label(DeleteItems); + case_label(FetchItems); + case_label(LinkItems); + case_label(ModifyItems); + case_label(MoveItems); + + case_label(CreateCollection); + case_label(CopyCollection); + case_label(DeleteCollection); + case_label(FetchCollections); + case_label(FetchCollectionStats); + case_label(ModifyCollection); + case_label(MoveCollection); + + case_label(Search); + case_label(SearchResult); + case_label(StoreSearch); + + case_label(CreateTag); + case_label(DeleteTag); + case_label(FetchTags); + case_label(ModifyTag); + + case_label(FetchRelations); + case_label(ModifyRelation); + case_label(RemoveRelations); + + case_label(SelectResource); + + case_label(StreamPayload); + case_label(ItemChangeNotification); + case_label(CollectionChangeNotification); + case_label(TagChangeNotification); + case_label(RelationChangeNotification); + case_label(SubscriptionChangeNotification); + case_label(DebugChangeNotification); + case_label(CreateSubscription); + case_label(ModifySubscription); + } +#undef case_label + stream << "\"reponse\": " << ((mType & Command::_ResponseBit) ? "true" : "false"); + stream << "\"}"; + return stream; +} + DataStream &operator<<(DataStream &stream, const Command &cmd) { return stream << cmd.mType; @@ -245,6 +307,18 @@ && mErrorMsg == other.mErrorMsg; } +QTextStream &Response::toJson(QTextStream &stream) const +{ + stream << "{" + << "\"parent\": "; + static_cast(this)->toJson(stream); + stream << "," + << "\"errcode\": " << mErrorCode << "," + << "\"errstr\": \"" << mErrorMsg << "\"" + << "}"; + return stream; +} + DataStream &operator<<(DataStream &stream, const Response &cmd) { return stream << static_cast(cmd) @@ -456,6 +530,33 @@ } } +QTextStream &ItemFetchScope::toJson(QTextStream &stream) const +{ + stream << "{ \"type\": \"fetchscope\"," + << "\"flags\": " << mFlags << "," + << "\"TagFetchScope\": ["; + for (const auto &tag : qAsConst(mTagFetchScope)) { + stream << "\"" << tag << "\","; + } + if (!mTagFetchScope.isEmpty()) { + stream.seek(-1); + } + stream << "]," + << "\"ChangedSince\": \"" << mChangedSince.toString() << "\"," + << "\"AncestorDepth\":" << mAncestorDepth << "," + << "\"RequestedParts\": ["; + + for (const auto &part : qAsConst(mRequestedParts)) { + stream << "\"" << part << "\","; + } + if (!mRequestedParts.isEmpty()) { + stream.seek(-1); + } + stream << "]" + << "}"; + return stream; +} + QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth) { switch (depth) { @@ -545,6 +646,29 @@ return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx; } +QTextStream &ScopeContext::toJson(QTextStream &stream) const +{ + stream << "{\"scopeContext\":"; + if (isEmpty()) { + stream << "\"empty\""; + } else if (hasContextId(ScopeContext::Tag)) { + stream << "\"tag\","; + stream << "\"TagID\": " << contextId(ScopeContext::Tag); + } else if (hasContextId(ScopeContext::Collection)) { + stream << "\"collection\""; + stream << "\"ColID\":" << contextId(ScopeContext::Collection); + } else if (hasContextRID(ScopeContext::Tag)) { + stream << "\"tagrid\""; + stream << "\"TagRID\": \"" << contextRID(ScopeContext::Tag) << "\""; + } else if (hasContextRID(ScopeContext::Collection)) { + stream << "\"colrid\","; + stream << "\"ColRID\": \"" << contextRID(ScopeContext::Collection) << "\""; + } + stream << "}"; + + return stream; +} + DataStream &operator<<(DataStream &stream, const ScopeContext &context) { // We don't have a custom generic DataStream streaming operator for QVariant @@ -751,6 +875,25 @@ return true; } +QTextStream &ChangeNotification::toJson(QTextStream &stream) const +{ + stream << "{" + << "\"parent\": "; + static_cast(this)->toJson(stream); + stream << "," + << "\"session\": \"" << mSessionId << "\"," + << "\"metadata\": ["; + for (const auto &m : qAsConst(mMetaData)) { + stream << "\"" << m << "\","; + } + if (!mMetaData.isEmpty()) { + stream.seek(-1); + } + stream << "]" + << "}"; + return stream; +} + DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf) { return stream << static_cast(ntf) diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -154,6 +154,7 @@ inline bool isValid() const { return type() != Invalid; } inline bool isResponse() const { return mType & _ResponseBit; } + QTextStream &toJson(QTextStream &stream) const; protected: explicit Command(quint8 type); @@ -211,6 +212,7 @@ inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } + QTextStream &toJson(QTextStream &stream) const; protected: explicit Response(Command::Type type); @@ -351,6 +353,7 @@ void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; + QTextStream &toJson(QTextStream &stream) const; private: AncestorDepth mAncestorDepth; // 2 bytes free @@ -429,6 +432,7 @@ return hasContextRID(type) ? ctx(type).toString() : QString(); } + QTextStream &toJson(QTextStream &stream) const; private: QVariant mColCtx; QVariant mTagCtx; @@ -502,6 +506,16 @@ && type == other.type; } + QTextStream &toJson(QTextStream &stream) const + { + stream << "{\n" + << " \"leftId\": " << leftId << ",\n" + << " \"rightId\": " << rightId << ",\n" + << " \"type\": \"" << type << "\"\n" + << "}"; + return stream; + } + qint64 leftId; qint64 rightId; QString type; @@ -524,6 +538,7 @@ static bool appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg); + QTextStream &toJson(QTextStream &stream) const; protected: explicit ChangeNotification(Command::Type type); ChangeNotification(const ChangeNotification &other); diff --git a/src/private/protocolgen/cppgenerator.cpp b/src/private/protocolgen/cppgenerator.cpp --- a/src/private/protocolgen/cppgenerator.cpp +++ b/src/private/protocolgen/cppgenerator.cpp @@ -347,6 +347,7 @@ mHeader << "\n"; } } + mHeader << " QTextStream &toJson(QTextStream &stream) const;\n"; // End of class mHeader << "protected:\n"; @@ -606,7 +607,7 @@ } else { mImpl << "type"; } - mImpl << "<< \"\\n\";\n" + mImpl << " << \"\\n\";\n" " }\n" " dbg.noquote() << \"]\\n\"\n"; } else { @@ -617,6 +618,95 @@ " return dbg;\n" "}\n" "\n"; + + // toJson + mImpl << "QTextStream &" << node->className() << "::toJson(QTextStream &stream) const\n" + "{\n" + " stream << \"{\";\n"; + if (!parentClass.isEmpty()) { + mImpl << " stream << \"\\\"parent\\\": \";\n" + << " static_cast(this)->toJson(stream);\n"; + } + + for (auto prop : qAsConst(serializeProperties)) { + mImpl << " stream << \"\\\"" << prop->name() << "\\\": \";\n"; + if (prop->isPointer()) { + mImpl << " " << prop->mVariableName() << "->toJson(stream);\n" + << " stream << \",\\n\";\n"; + } else if (TypeHelper::isContainer(prop->type())) { + mImpl << " stream << \" [\\n\";\n" + " for (const auto &type : qAsConst(" << prop->mVariableName() << ")) {\n"; + if (TypeHelper::isPointerType(TypeHelper::containerType(prop->type()))) { + mImpl << " stream << \" \";\n" + " type->toJson(stream);\n" + " stream << \",\\n\";\n"; + } else if (TypeHelper::isNumericType(TypeHelper::containerType(prop->type()))) { + mImpl << " stream<< \" \" << type << \",\\n\";\n"; + } else if (TypeHelper::isBoolType(TypeHelper::containerType(prop->type()))) { + mImpl << " stream << \" \" << (type?\"true\":\"false\") << \",\\n\";\n"; + } else if (TypeHelper::isBuiltInType(TypeHelper::containerType(prop->type()))) { + if (TypeHelper::containerType(prop->type()) == QStringLiteral("Akonadi::Protocol::ChangeNotification::Relation")) { + mImpl << " stream << \" \";\n" + " type.toJson(stream);\n" + " stream << \",\\n\";\n"; + } else { + mImpl << " stream << \" \\\"\" << type << \"\\\",\\n\";\n"; + } + } else { + mImpl << " stream << \" \";\n" + " type.toJson(stream);\n" + " stream << \",\\n\";\n"; + } + mImpl << " }\n" + " if (!"<< prop->mVariableName() <<".isEmpty()) {\n" + " stream.seek(-2);\n" + " }\n" + " stream << \"],\\n\";\n"; + } else if (TypeHelper::isNumericType(prop->type())) { + mImpl << " stream << " << prop->mVariableName() << " << \",\\n\";\n"; + } else if (TypeHelper::isBoolType(prop->type())) { + mImpl << " stream << (" << prop->mVariableName() << "?\"true\":\"false\") << \",\\n\";\n"; + } else if (TypeHelper::isBuiltInType(prop->type())) { + if (prop->type() == QStringLiteral("QStringList")) { + mImpl << " stream << \"[\" << " << prop->mVariableName() << ".join(QStringLiteral(\"\\\", \\\"\")) << \"],\\n\";\n"; + } else if (prop->type() == QStringLiteral("QDateTime")) { + mImpl << " stream << " << prop->mVariableName() << ".toString() << \",\\n\";\n"; + } else if (prop->type() == QStringLiteral("Scope")) { + mImpl << " " << prop->mVariableName() << ".toJson(stream);\n" + << " stream << \",\\n\";\n"; + } else if (prop->type() == QStringLiteral("Tristate")) { + mImpl << " switch (" << prop->mVariableName() << ") {\n;" + " case Tristate::True:\n" + " return stream << \"True\";\n" + " case Tristate::False:\n" + " return stream << \"False\";\n" + " case Tristate::Undefined:\n" + " return stream << \"Undefined\";\n" + " }\n" + " stream << \",\\n\";\n"; + } else if (prop->type() == QStringLiteral("Akonadi::Protocol::Attributes")) { + mImpl << " stream << \"{\";\n" + " for ( auto key : " << prop->mVariableName() << ".keys() ) {\n" + " stream << \"\\\"\" << key << \"\\\": \\\"\" << " << prop->mVariableName() << ".value(key) << \"\\\",\\n\";\n" + " }\n" + " if (!" << prop->mVariableName() << ".isEmpty()) {\n" + " stream.seek(-2);\n" + " }\n" + " stream << \"},\\n\";\n"; + } else { + mImpl << " stream << \"\\\"\" << " << prop->mVariableName() << " << \"\\\",\\n\";\n"; + } + } else { + mImpl << " " << prop->mVariableName() << ".toJson(stream);\n" + " stream << \",\\n\";\n"; + } + } + mImpl << " stream.seek(-2);" + " stream << \"}\";\n" + "\n" + " return stream;\n" + "}\n" + "\n"; } void CppGenerator::writeImplPropertyDependencies(const PropertyNode* node) diff --git a/src/private/scope.cpp b/src/private/scope.cpp --- a/src/private/scope.cpp +++ b/src/private/scope.cpp @@ -305,6 +305,46 @@ return d->gidSet.at(0); } +QTextStream &Akonadi::Scope::HRID::toJson(QTextStream &stream) const +{ + stream << "{\"ID\": " << id << ", \"RemoteID\": \"" << remoteId << "\"}"; + return stream; +} + +QTextStream &Scope::toJson(QTextStream& stream) const +{ + stream << "{"; + switch (scope()) { + case Scope::Uid: + stream << "\"UID\": \"" << uidSet().toImapSequenceSet() << "\""; + break; + case Scope::Rid: + stream << "\"RID\": [\"" << ridSet().join(QStringLiteral("\" , \"")) << "\"]"; + break; + case Scope::Gid: + stream << "\"GID\": [\"" << gidSet().join(QStringLiteral("\" , \"")) << "\"]"; + break; + case Scope::HierarchicalRid: + { + const auto &chain = hridChain(); + stream << "\"HRID\": ["; + for (const auto &hrid : chain) { + hrid.toJson(stream); + stream << ","; + } + if (!chain.isEmpty()) { + stream.seek(-1); + } + stream << "]"; + } + break; + default: + stream << "None"; + } + stream << "}"; + return stream; +} + Protocol::DataStream &operator<<(Protocol::DataStream &stream, const Akonadi::Scope &scope) { stream << (quint8) scope.d->scope; diff --git a/src/private/scope_p.h b/src/private/scope_p.h --- a/src/private/scope_p.h +++ b/src/private/scope_p.h @@ -29,6 +29,7 @@ #include class QStringList; +class QTextStream; namespace Akonadi { @@ -66,6 +67,8 @@ bool isEmpty() const; bool operator==(const HRID &other) const; + QTextStream &toJson(QTextStream &stream) const; + qint64 id; QString remoteId; }; @@ -110,6 +113,7 @@ QString rid() const; QString gid() const; + QTextStream &toJson(QTextStream &stream) const; private: QSharedDataPointer d; friend class ScopePrivate;