diff --git a/DbMessaging.pro b/DbMessaging.pro new file mode 100644 index 0000000..c5e0dbf --- /dev/null +++ b/DbMessaging.pro @@ -0,0 +1,10 @@ +TEMPLATE = subdirs + +SUBDIRS += messagingclient \ + messaginglib \ + messagingserver \ + messagingtest + +messagingclient.depends += messaginglib +messagingserver.depends += messaginglib +messagingtest.depends += messaginglib diff --git a/messagingclient/main.cpp b/messagingclient/main.cpp new file mode 100644 index 0000000..6334f40 --- /dev/null +++ b/messagingclient/main.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "messages/mymessage.h" + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + MyMessage original("Daniel", QDate(1996, 11, 12), QDateTime::currentDateTime(), 21, 80); + MyMessage copy(original); + + original.setName("Peter"); + + QVariantMap delta; + original.copyTouchedTo(delta); + original.setTouched(false); + + qDebug() << "before applying delta"; + copy.debug(); + + copy.apply(delta); + + qDebug() << "after applying delta"; + copy.debug(); + + return app.exec(); +} diff --git a/messagingclient/messagingclient.pro b/messagingclient/messagingclient.pro new file mode 100644 index 0000000..c05bc1e --- /dev/null +++ b/messagingclient/messagingclient.pro @@ -0,0 +1,20 @@ +QT += core network +QT -= gui widgets + +DBLIBS += messaginglib + +TARGET = messagingclient + +PROJECT_ROOT = ../.. + +SOURCES += main.cpp + +HEADERS += + +FORMS += + +RESOURCES += + +TRANSLATIONS += + +include($${PROJECT_ROOT}/app.pri) diff --git a/messaginglib/dbmsgbase.cpp b/messaginglib/dbmsgbase.cpp new file mode 100644 index 0000000..ad62013 --- /dev/null +++ b/messaginglib/dbmsgbase.cpp @@ -0,0 +1,100 @@ +#include "dbmsgbase.h" + +#include + +#include + +#include "dbmsgfieldbase.h" + +const QString DbMsgBase::m_clearedFieldsName(QStringLiteral("__CLEARED_FIELDS")); + +DbMsgBase::DbMsgBase() +{ +} + +DbMsgBase::~DbMsgBase() +{ +} + +bool DbMsgBase::touched() const +{ + const auto fields = getFields(); + return std::any_of(fields.cbegin(), fields.cend(), [](const DbMsgFieldBase *field) { return field->touched(); }); +} + +void DbMsgBase::setTouched(bool touched) +{ + for(DbMsgFieldBase *field : getFields()) + field->setTouched(touched); +} + +void DbMsgBase::debug() const +{ + const auto fields = getFields(); + for(auto iter = fields.cbegin(); iter != fields.cend(); iter++) + qDebug() << iter.key() << iter.value()->toVariant() << iter.value()->touched(); +} + +void DbMsgBase::copyTo(QVariantMap &variantMap) const +{ + const auto fields = getFields(); + for(auto iter = fields.cbegin(); iter != fields.cend(); iter++) + { + const auto key = iter.key(); + const auto field = iter.value(); + const auto hasValue = field->hasValue(); + const auto variant = field->toVariant(); + if(hasValue) + variantMap.insert(key, variant); + else + qWarning() << key << "has no value for full transmission!"; + } +} + +void DbMsgBase::copyTouchedTo(QVariantMap &variantMap) const +{ + QStringList clearedFields; + + const auto fields = getFields(); + for(auto iter = fields.cbegin(); iter != fields.cend(); iter++) + if(iter.value()->touched()) + { + if(iter.value()->hasValue()) + variantMap.insert(iter.key(), iter.value()->toVariant()); + else + clearedFields.append(iter.key()); + } + + if(!clearedFields.isEmpty()) + variantMap.insert(m_clearedFieldsName, clearedFields); +} + +void DbMsgBase::apply(const QVariantMap &variantMap) +{ + const auto fields = getFields(); + for(auto iter = variantMap.cbegin(); iter != variantMap.cend(); iter++) + { + if(iter.key() == m_clearedFieldsName) + { + Q_ASSERT(iter.value().type() == QVariant::StringList); + for(const auto &clearedField : iter.value().toStringList()) + { + Q_ASSERT(fields.contains(clearedField)); + const auto field = fields.value(clearedField); + if(field->touched()) + qWarning() << "delta message contained field which has been touched!"; + field->clear(); + field->setTouched(false); + } + } + else + { + Q_ASSERT(fields.contains(iter.key())); + const auto field = fields.value(iter.key()); + if(field->touched()) + qWarning() << "delta message contained field which has been touched!"; + field->setVariant(iter.value()); + field->setTouched(false); + } + } +} diff --git a/messaginglib/dbmsgbase.h b/messaginglib/dbmsgbase.h new file mode 100644 index 0000000..5f6000b --- /dev/null +++ b/messaginglib/dbmsgbase.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include "messaginglib_global.h" +#include "dbmsgmacros.h" + +class DbMsgFieldBase; + +class MESSAGINGLIB_EXPORT DbMsgBase +{ + static const QString m_clearedFieldsName; + +public: + DbMsgBase(); + virtual ~DbMsgBase(); + + bool touched() const; + void setTouched(bool touched); + + void debug() const; + + void copyTo(QVariantMap &variantMap) const; + void copyTouchedTo(QVariantMap &variantMap) const; + + void apply(const QVariantMap &variantMap); + +protected: + virtual QMap getFields() = 0; + virtual QMap getFields() const = 0; +}; diff --git a/messaginglib/dbmsgfield.h b/messaginglib/dbmsgfield.h new file mode 100644 index 0000000..f3f4cc0 --- /dev/null +++ b/messaginglib/dbmsgfield.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "dbmsgfieldbase.h" +#include "messaginglib_global.h" + +template +class DbMsgField : public DbMsgFieldBase +{ +public: + DbMsgField(); + DbMsgField(const T &value); + + const T &getValue() const; + void setValue(const T &value); + + void clear() override; + QVariant toVariant() const override; + void setVariant(const QVariant &variant) override; + +private: + T m_value; +}; + +template +DbMsgField::DbMsgField() : + DbMsgFieldBase() +{ +} + +template +DbMsgField::DbMsgField(const T &value) : + DbMsgFieldBase(), + m_value(value) +{ + setHasValue(true); +} + +template +const T &DbMsgField::getValue() const +{ + return m_value; +} + +template +void DbMsgField::setValue(const T &value) +{ + m_value = value; + setHasValue(true); + setTouched(true); +} + +template +void DbMsgField::clear() +{ + DbMsgFieldBase::clear(); + m_value = T(); +} + +template +QVariant DbMsgField::toVariant() const +{ + return getValue(); +} + +template +void DbMsgField::setVariant(const QVariant &variant) +{ + Q_ASSERT(variant.type() == qMetaTypeId()); + m_value = variant.value(); +} diff --git a/messaginglib/dbmsgfieldbase.cpp b/messaginglib/dbmsgfieldbase.cpp new file mode 100644 index 0000000..46e33b3 --- /dev/null +++ b/messaginglib/dbmsgfieldbase.cpp @@ -0,0 +1,38 @@ +#include "dbmsgfieldbase.h" + +DbMsgFieldBase::DbMsgFieldBase() : + m_hasValue(false), m_touched(false) +{ +} + +DbMsgFieldBase::~DbMsgFieldBase() +{ +} + +bool DbMsgFieldBase::hasValue() const +{ + return m_hasValue; +} + +void DbMsgFieldBase::clear() +{ + m_hasValue = false; +} + +bool DbMsgFieldBase::touched() const +{ + return m_touched; +} + +void DbMsgFieldBase::setTouched(bool touched) +{ + m_touched = touched; +} + +void DbMsgFieldBase::setHasValue(bool hasValue) +{ + if(m_hasValue && !hasValue) + clear(); + else + m_hasValue = hasValue; +} diff --git a/messaginglib/dbmsgfieldbase.h b/messaginglib/dbmsgfieldbase.h new file mode 100644 index 0000000..c60d3f4 --- /dev/null +++ b/messaginglib/dbmsgfieldbase.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "messaginglib_global.h" + +class MESSAGINGLIB_EXPORT DbMsgFieldBase +{ +public: + DbMsgFieldBase(); + virtual ~DbMsgFieldBase(); + + bool hasValue() const; + virtual void clear(); + + bool touched() const; + void setTouched(bool touched); + + virtual QVariant toVariant() const = 0; + virtual void setVariant(const QVariant &variant) = 0; + +protected: + void setHasValue(bool hasValue); + +private: + bool m_hasValue; + bool m_touched; +}; diff --git a/messaginglib/dbmsgmacros.h b/messaginglib/dbmsgmacros.h new file mode 100644 index 0000000..dec056b --- /dev/null +++ b/messaginglib/dbmsgmacros.h @@ -0,0 +1,51 @@ +#pragma once + +#define DECLARE_DBFIELD(TYPE, NAME, UPPER_NAME) \ +private: \ + DbMsgField m_##NAME;\ + \ +public: \ + inline TYPE get##UPPER_NAME() const { return m_##NAME.getValue(); } \ + inline void set##UPPER_NAME(const TYPE &NAME) { m_##NAME.setValue(NAME); } \ + inline bool has##UPPER_NAME() const { return m_##NAME.hasValue(); } \ + inline void clear##UPPER_NAME() { m_##NAME.clear(); } \ + \ + inline DbMsgField &NAME##Field() { return m_##NAME; } \ + inline const DbMsgField &NAME##Field() const { return m_##NAME; } \ + inline DbMsgFieldBase &NAME##FieldBase() { return m_##NAME; } \ + inline const DbMsgFieldBase &NAME##FieldBase() const { return m_##NAME; } + +#define DECLARE_DBMESSAGE(ClassName) \ +public: \ + ClassName() = default; \ + ClassName(const ClassName &) = default; \ + ClassName(ClassName &&) = default; \ +protected: \ + QMap getFields() override; \ + QMap getFields() const override; \ +private: \ + typedef DbMsgFieldBase &(ClassName::*FieldGetterMethod)(); \ + static const QMap ALL_FIELDS; + +#define IMPLEMENT_DBMESSAGE(ClassName) \ + QMap ClassName::getFields() \ + { \ + QMap fields; \ + \ + for(auto iter = ALL_FIELDS.cbegin(); iter != ALL_FIELDS.cend(); iter++) \ + fields.insert(iter.key(), &(this->*iter.value())()); \ + \ + return fields; \ + } \ + \ + QMap ClassName::getFields() const \ + { \ + QMap fields; \ + \ + for(auto iter = ALL_FIELDS.cbegin(); iter != ALL_FIELDS.cend(); iter++) \ + fields.insert(iter.key(), &(const_cast(this)->*iter.value())()); \ + \ + return fields; \ + } \ + \ + const QMap ClassName::ALL_FIELDS diff --git a/messaginglib/messages/mymessage.cpp b/messaginglib/messages/mymessage.cpp new file mode 100644 index 0000000..9dc539d --- /dev/null +++ b/messaginglib/messages/mymessage.cpp @@ -0,0 +1,15 @@ +#include "mymessage.h" + +IMPLEMENT_DBMESSAGE(MyMessage) { + { "name", &MyMessage::nameFieldBase }, + { "birthday", &MyMessage::birthdayFieldBase }, + { "sendTimestamp", &MyMessage::sendTimestampFieldBase }, + { "age", &MyMessage::ageFieldBase }, + { "weight", &MyMessage::weightFieldBase } +}; + +MyMessage::MyMessage(const QString &name, const QDate &birthday, const QDateTime &sendTimestamp, int age, double weight) : + DbMsgBase(), + m_name(name), m_birthday(birthday), m_sendTimestamp(sendTimestamp), m_age(age), m_weight(weight) +{ +} diff --git a/messaginglib/messages/mymessage.h b/messaginglib/messages/mymessage.h new file mode 100644 index 0000000..60de676 --- /dev/null +++ b/messaginglib/messages/mymessage.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "dbmsgbase.h" +#include "messaginglib_global.h" +#include "dbmsgfield.h" + +class DbMsgFieldBase; + +class MESSAGINGLIB_EXPORT MyMessage : public DbMsgBase +{ + DECLARE_DBMESSAGE(MyMessage) + +public: + MyMessage(const QString &name, const QDate &birthday, const QDateTime &sendTimestamp, int age, double weight); + + DECLARE_DBFIELD(QString, name, Name) + DECLARE_DBFIELD(QDate, birthday, Birthday) + DECLARE_DBFIELD(QDateTime, sendTimestamp, SendTimestamp) + DECLARE_DBFIELD(int, age, Age) + DECLARE_DBFIELD(double, weight, Weight) +}; diff --git a/messaginglib/messaginglib.pro b/messaginglib/messaginglib.pro new file mode 100644 index 0000000..48c4f91 --- /dev/null +++ b/messaginglib/messaginglib.pro @@ -0,0 +1,28 @@ +QT += core network +QT -= gui widgets + +DBLIBS += + +PROJECT_ROOT = ../.. + +DEFINES += MESSAGINGLIB_LIBRARY + +SOURCES += \ + dbmsgbase.cpp \ + dbmsgfieldbase.cpp \ + messages/mymessage.cpp + +HEADERS += messaginglib_global.h \ + dbmsgbase.h \ + dbmsgfield.h \ + dbmsgfieldbase.h \ + dbmsgmacros.h \ + messages/mymessage.h + +FORMS += + +RESOURCES += + +TRANSLATIONS += + +include($${PROJECT_ROOT}/lib.pri) diff --git a/messaginglib/messaginglib_global.h b/messaginglib/messaginglib_global.h new file mode 100644 index 0000000..2c65679 --- /dev/null +++ b/messaginglib/messaginglib_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(MESSAGINGLIB_LIBRARY) +# define MESSAGINGLIB_EXPORT Q_DECL_EXPORT +#else +# define MESSAGINGLIB_EXPORT Q_DECL_IMPORT +#endif diff --git a/messagingserver/main.cpp b/messagingserver/main.cpp new file mode 100644 index 0000000..1a46e00 --- /dev/null +++ b/messagingserver/main.cpp @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + qDebug() << "hello from server"; + + return 0; +} diff --git a/messagingserver/messagingserver.pro b/messagingserver/messagingserver.pro new file mode 100644 index 0000000..5f24eac --- /dev/null +++ b/messagingserver/messagingserver.pro @@ -0,0 +1,20 @@ +QT += core network +QT -= gui widgets + +DBLIBS += messaginglib + +TARGET = messagingserver + +PROJECT_ROOT = ../.. + +SOURCES += main.cpp + +HEADERS += + +FORMS += + +RESOURCES += + +TRANSLATIONS += + +include($${PROJECT_ROOT}/app.pri) diff --git a/messagingtest/messagingtest.pro b/messagingtest/messagingtest.pro new file mode 100644 index 0000000..f73e16a --- /dev/null +++ b/messagingtest/messagingtest.pro @@ -0,0 +1,21 @@ +QT += core testlib +QT -= gui widgets + +DBLIBS += messaginglib + +#this makes install step fail +# CONFIG += testcase + +PROJECT_ROOT = ../.. + +SOURCES += tst_messagingtest.cpp + +HEADERS += + +FORMS += + +RESOURCES += + +TRANSLATIONS += + +include($${PROJECT_ROOT}/app.pri) diff --git a/messagingtest/tst_messagingtest.cpp b/messagingtest/tst_messagingtest.cpp new file mode 100644 index 0000000..e8fe5d8 --- /dev/null +++ b/messagingtest/tst_messagingtest.cpp @@ -0,0 +1,35 @@ +#include + +// add necessary includes here + +class MessagingTest : public QObject +{ + Q_OBJECT + +public: + MessagingTest(); + ~MessagingTest(); + +private slots: + void test_case1(); + +}; + +MessagingTest::MessagingTest() +{ + +} + +MessagingTest::~MessagingTest() +{ + +} + +void MessagingTest::test_case1() +{ + +} + +QTEST_APPLESS_MAIN(MessagingTest) + +#include "tst_messagingtest.moc"