소스 검색

Add simple chat example

- Implement simple chat server and client
Alexey Edelev 5 년 전
부모
커밋
e431b749c2

+ 3 - 0
CMakeLists.txt

@@ -74,6 +74,7 @@ add_subdirectory("src/generator")
 
 set(QTPROTOBUF_EXECUTABLE $<TARGET_FILE:${GENERATOR_TARGET}>)
 
+set(MAKE_TESTS OFF)
 if(DEFINED $ENV{MAKE_TESTS})
     set(MAKE_TESTS $ENV{MAKE_TESTS})
 elseif(NOT DEFINED MAKE_TESTS)
@@ -132,6 +133,8 @@ endif()
 if(MAKE_EXAMPLES)
     add_subdirectory("examples/addressbook")
     add_subdirectory("examples/addressbookserver")
+    add_subdirectory("examples/simplechat")
+    add_subdirectory("examples/simplechatserver")
 endif()
 
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/QtProtobufConfig.cmake DESTINATION ${TARGET_CMAKE_DIR} PERMISSIONS OWNER_WRITE GROUP_READ WORLD_READ)

+ 35 - 0
examples/simplechat/CMakeLists.txt

@@ -0,0 +1,35 @@
+set(TARGET simplechat)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+find_package(Qt5 COMPONENTS Core Quick Network REQUIRED)
+
+set(GENERATED_HEADERS
+    chatmessage.h
+    chatmessages.h
+    user.h
+    users.h
+    simplereply.h
+    none.h
+    simplechatclient.h)
+
+file(GLOB PROTO_FILES ABSOLUTE ${CMAKE_CURRENT_SOURCE_DIR}/proto/simplechat.proto)
+
+generate_qtprotobuf(TARGET ${TARGET}
+    OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated
+    PROTO_FILES ${PROTO_FILES}
+    GENERATED_HEADERS ${GENERATED_HEADERS})
+
+file(GLOB SOURCES main.cpp
+    simplechatengine.cpp
+    universallistmodelbase.cpp
+    universallistmodel.cpp)
+
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../simplechatserver/cert.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(${TARGET} ${SOURCES} resources.qrc)
+add_dependencies(${TARGET} ${QtProtobuf_GENERATED})
+target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
+target_link_libraries(${TARGET} ${GRPC_LIBRARY_TARGET} ${PROTOBUF_LIBRARY_TARGET} ${QtProtobuf_GENERATED} Qt5::Quick Qt5::Qml)

+ 16 - 0
examples/simplechat/ChatInputField.qml

@@ -0,0 +1,16 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.4
+TextField {
+    id: _inputField
+    width: 200
+    color: "#FFFFFF"
+    font.pointSize: 14
+    background: Rectangle {
+        radius: 20
+        border {
+            width: 2
+            color: _inputField.focus ? "#E91E63" : "#FFFFFF"
+        }
+        color: "#424242"
+    }
+}

+ 45 - 0
examples/simplechat/ChatView.qml

@@ -0,0 +1,45 @@
+import QtQuick 2.4
+import examples.simplechat 1.0
+
+Rectangle {
+    anchors.fill: parent
+    color: "#303030"
+    ListView {
+        anchors.top: parent.top
+        anchors.bottom: _inputField.top
+        model: scEngine.messages
+        delegate: Item {
+            height: childrenRect.height
+            width: _inputField.width
+            Text {
+                id: _userName
+                font.pointSize: 12
+                font.weight: Font.Bold
+                color: "#ffffff"
+                text: model.modelData.from + ": "
+            }
+
+            Text {
+                anchors.left: _userName.right
+                font.pointSize: 12
+                color: "#ffffff"
+                text: scEngine.getText(model.modelData.content)
+            }
+        }
+        onCountChanged: {
+            positionViewAtEnd()
+        }
+    }
+
+    ChatInputField {
+        id: _inputField
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+        placeholderText: qsTr("Start type here")
+        onAccepted: {
+            scEngine.sendMessage(_inputField.text)
+            _inputField.text = ""
+        }
+    }
+}

BIN
examples/simplechat/img/arrow.png


+ 66 - 0
examples/simplechat/main.cpp

@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <QGuiApplication>
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+
+#include <qtprotobuf.h>
+#include <chatmessage.h>
+#include <user.h>
+#include <chatmessages.h>
+#include "simplechatengine.h"
+
+#include <QMetaProperty>
+#include <QQmlPropertyMap>
+#include <QCryptographicHash>
+
+using namespace qtprotobuf::examples;
+
+int main(int argc, char *argv[])
+{
+    QGuiApplication app(argc, argv);
+
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    qtprotobuf::QtProtobuf::init();
+    ChatMessages::registerTypes();
+    ChatMessage::registerTypes();
+    User::registerTypes();
+    Users::registerTypes();
+
+    qmlRegisterUncreatableType<ChatMessageModel>("examples.simplechat",  1, 0, "ChatMessageModel", "");
+
+    QQmlApplicationEngine engine;
+
+    SimpleChatEngine scEngine;
+    engine.rootContext()->setContextProperty("scEngine", &scEngine);
+    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+    if (engine.rootObjects().isEmpty())
+        return -1;
+
+    return app.exec();
+}

+ 102 - 0
examples/simplechat/main.qml

@@ -0,0 +1,102 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import QtQuick 2.9
+import QtQuick.Controls 2.4
+import QtGraphicalEffects 1.0
+
+import examples.simplechat 1.0
+
+ApplicationWindow {
+    id: mainWindow
+    visible: true
+    width: 640
+    height: 480
+    title: qsTr("QtProtobuf Simple Chat Example")
+    Rectangle {
+        id: background
+        anchors.fill: parent
+        color: "#303030"
+    }
+
+    Column {
+        spacing: 5
+        anchors.centerIn: parent
+        ChatInputField {
+            id: _loginField
+            width: 200
+            placeholderText: qsTr("Login")
+        }
+        ChatInputField {
+            id: _passwordField
+            echoMode: TextInput.Password
+            placeholderText: qsTr("Password")
+        }
+        Button {
+            id: _pressedControl
+            anchors.horizontalCenter: parent.horizontalCenter
+            width: 40
+            height: 40
+            background: Rectangle {
+                radius: _pressedControl.width / 2
+                border {
+                    width: 2
+                    color: _pressedControl.pressed ? "#E91E63" : "#ffffff"
+                }
+                color:"#00000000"
+                rotation: 90
+                Image {
+                    id: _icon
+                    anchors.centerIn: parent
+                    source: "qrc:/img/arrow.png"
+                    width: sourceSize.width
+                    height: sourceSize.width
+                    smooth: true
+                    visible: false
+                }
+                ColorOverlay {
+                    anchors.fill: _icon
+                    source: _icon
+                    color: _pressedControl.pressed ? "#E91E63" : "#ffffff"
+                }
+            }
+
+            onClicked: {
+                scEngine.login(_loginField.text, _passwordField.text)
+            }
+        }
+    }
+
+    ChatView {
+        id: _chatView
+        visible: false
+        Connections {
+            target: scEngine
+            onLoggedIn: {
+                _chatView.visible = true
+            }
+        }
+    }
+}

+ 63 - 0
examples/simplechat/proto/simplechat.proto

@@ -0,0 +1,63 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+syntax="proto3";
+
+package qtprotobuf.examples;
+
+message ChatMessage {
+    enum ContentType {
+        Unknown = 0;
+        Text = 1;
+        Image = 2;
+    };
+    uint64 timestamp = 1;
+    bytes content = 2;
+    ContentType type = 3;
+    string from = 4;
+}
+
+message ChatMessages {
+    repeated ChatMessage messages = 1;
+}
+
+message User {
+    string name = 1;
+    string password = 2;
+}
+
+message Users {
+    repeated User users = 1;
+}
+
+message None {
+}
+
+service SimpleChat {
+    rpc usersOnline(None) returns (stream Users) {}
+    rpc messageList(None) returns (stream ChatMessages) {}
+    rpc sendMessage(ChatMessage) returns (None) {}
+    rpc login(User) returns (None) {}
+}

+ 8 - 0
examples/simplechat/resources.qrc

@@ -0,0 +1,8 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+        <file>img/arrow.png</file>
+        <file>ChatInputField.qml</file>
+        <file>ChatView.qml</file>
+    </qresource>
+</RCC>

+ 82 - 0
examples/simplechat/simplechatengine.cpp

@@ -0,0 +1,82 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "simplechatengine.h"
+
+#include <http2channel.h>
+#include <insecurecredentials.h>
+#include <sslcredentials.h>
+
+#include <QDebug>
+#include <QFile>
+#include <QSslConfiguration>
+#include <QCryptographicHash>
+#include <QDateTime>
+
+using namespace qtprotobuf::examples;
+
+class AuthCredentials : public qtprotobuf::CallCredentials
+{
+public:
+    AuthCredentials(const QString &userName, const QString &password) :
+        CallCredentials(CredentialMap{{QLatin1String("user-name"), QVariant::fromValue(userName)},
+                                      {QLatin1String("user-password"), QVariant::fromValue(password)}}) {}
+};
+
+SimpleChatEngine::SimpleChatEngine(QObject *parent) : QObject(parent), m_client(new SimpleChatClient)
+{
+
+}
+
+SimpleChatEngine::~SimpleChatEngine()
+{
+    delete m_client;
+}
+
+void SimpleChatEngine::login(const QString &name, const QString &password)
+{
+    QSslConfiguration conf = QSslConfiguration::defaultConfiguration();
+    QFile certFile("cert.pem");
+    certFile.open(QIODevice::ReadOnly);
+    QByteArray cert = certFile.readAll();
+    conf.setCaCertificates({QSslCertificate(cert)});
+    conf.setProtocol(QSsl::TlsV1_2);
+    conf.setAllowedNextProtocols({QSslConfiguration::ALPNProtocolHTTP2});
+
+    std::shared_ptr<qtprotobuf::AbstractChannel> channel(new qtprotobuf::Http2Channel("localhost", 65002, qtprotobuf::SslCredentials(conf) |
+                                                                                      AuthCredentials(name, QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Md5).toHex())));
+
+    m_client->attachChannel(channel);
+    m_client->subscribeMessageListUpdates(None());
+    QObject::connect(m_client, &SimpleChatClient::messageListUpdated, this, [this](const qtprotobuf::examples::ChatMessages &messages){
+        loggedIn();
+        m_messages.reset(messages.messages());
+    });
+}
+
+void SimpleChatEngine::sendMessage(const QString &content)
+{
+    m_client->sendMessage(ChatMessage(QDateTime::currentMSecsSinceEpoch(), content.toUtf8(), ChatMessage::ContentType::Text));
+}

+ 66 - 0
examples/simplechat/simplechatengine.h

@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <chatmessages.h>
+#include "simplechatclient.h"
+
+#include "universallistmodel.h"
+
+
+namespace qtprotobuf {
+namespace examples {
+
+using ChatMessageModel = UniversalListModel<qtprotobuf::examples::ChatMessage>;
+
+class SimpleChatEngine : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(qtprotobuf::examples::ChatMessageModel *messages READ messages CONSTANT)
+
+    ChatMessageModel m_messages;
+    SimpleChatClient *m_client;
+public:
+    explicit SimpleChatEngine(QObject *parent = nullptr);
+    ~SimpleChatEngine();
+    Q_INVOKABLE void login(const QString &name, const QString &password);
+    Q_INVOKABLE void sendMessage(const QString &message);
+
+    Q_INVOKABLE QString getText(const QByteArray &data) { return QString::fromUtf8(data); }
+
+    ChatMessageModel *messages()
+    {
+        return &m_messages;
+    }
+Q_SIGNALS:
+    void loggedIn();
+    void authFailed();
+};
+
+}
+}
+Q_DECLARE_METATYPE(qtprotobuf::examples::ChatMessageModel*)

+ 1 - 0
examples/simplechat/universallistmodel.cpp

@@ -0,0 +1 @@
+#include "universallistmodel.h"

+ 203 - 0
examples/simplechat/universallistmodel.h

@@ -0,0 +1,203 @@
+#pragma once
+
+#include <universallistmodelbase.h>
+
+#include <QObject>
+#include <QList>
+#include <QHash>
+#include <QSharedPointer>
+#include <QMetaProperty>
+#include <QMetaObject>
+
+#include <QDebug>
+
+
+/*!
+ * \brief Universal list model is QObject-base list model abstraction.
+ * It exposes all objects properties as data-roles.
+ */
+template <typename T>
+class UniversalListModel : public UniversalListModelBase
+{
+public:
+    UniversalListModel(QList<QSharedPointer<T>> container = {}, QObject* parent = 0) : UniversalListModelBase(parent) {
+        reset(container);
+    }
+    ~UniversalListModel() {
+        clear();
+    }
+
+    int rowCount(const QModelIndex &parent) const override {
+        Q_UNUSED(parent)
+        return count();
+    }
+
+    int count() const override {
+        return m_container.count();
+    }
+
+    QHash<int, QByteArray> roleNames() const override {
+        if(s_roleNames.isEmpty()) {
+            int propertyCount = T::staticMetaObject.propertyCount();
+            for(int i = 1; i < propertyCount; i++) {
+                s_roleNames.insert(Qt::UserRole + i, T::staticMetaObject.property(i).name());
+            }
+            s_roleNames[propertyCount] = "modelData";
+        }
+        return s_roleNames;
+    }
+
+    QVariant data(const QModelIndex &index, int role) const override
+    {
+        int row = index.row();
+
+        if(row < 0 || row >= m_container.count() || m_container.at(row).isNull()) {
+            return QVariant();
+        }
+
+        T* dataPtr = m_container.at(row).data();
+
+        if(s_roleNames.value(role) == "modelData") {
+            return QVariant::fromValue(dataPtr);
+        }
+        return dataPtr->property(s_roleNames.value(role));
+    }
+
+    /*!
+     * \brief append
+     * \param value
+     * \return
+     */
+    int append(T* value) {
+        Q_ASSERT_X(value != nullptr, fullTemplateName(), "Trying to add member of NULL");
+
+        if(m_container.indexOf(value) >= 0) {
+#ifdef DEBUG
+            qDebug() << fullTemplateName() << "Member already exists";
+#endif
+            return -1;
+        }
+        beginInsertRows(QModelIndex(), m_container.count(), m_container.count());
+        m_container.append(QSharedPointer<T>(value));
+        emit countChanged();
+        endInsertRows();
+        return m_container.count() - 1;
+    }
+
+    /*!
+     * \brief prepend
+     * \param value
+     * \return
+     */
+    int prepend(T* value) {
+        Q_ASSERT_X(value != nullptr, fullTemplateName(), "Trying to add member of NULL");
+
+        if(m_container.indexOf(value) >= 0) {
+#ifdef DEBUG
+            qDebug() << fullTemplateName() << "Member already exists";
+#endif
+            return -1;
+        }
+        beginInsertRows(QModelIndex(), 0, 0);
+        m_container.prepend(QSharedPointer<T>(value));
+        emit countChanged();
+        endInsertRows();
+        return 0;
+    }
+
+    /*!
+     * \brief remove
+     * \param value
+     */
+    void remove(T* value) {
+        Q_ASSERT_X(value != nullptr, fullTemplateName(), ": Trying to remove member of NULL");
+
+        int valueIndex = m_container.indexOf(value);
+
+        remove(valueIndex);
+    }
+
+    void remove(int valueIndex) override {
+        if(valueIndex >= 0) {
+            beginRemoveRows(QModelIndex(), valueIndex, valueIndex);
+            m_container.removeAt(valueIndex);
+            emit countChanged();
+            endRemoveRows();
+        }
+    }
+
+    /*!
+     * \brief Resets container with new container passed as parameter
+     * \param container a data for model. Should contain QSharedPointer's to objects.
+     * Passing empty container makes model empty. This method should be used to cleanup model.
+     */
+    void reset(const QList<QSharedPointer<T> >& container) {
+        beginResetModel();
+        clear();
+        m_container = container;
+        emit countChanged();
+        endResetModel();
+    }
+
+    /*!
+     * \brief Returns the item at index position i in the list. i must be a valid index position in the list (i.e., 0 <= i < rowCount()).
+     * This function is very fast (constant time).
+     * \param i index of looking object
+     * \return Object at provided index
+     */
+    T* at(int i) const {
+        return m_container.at(i);
+    }
+
+    /*!
+     * \brief Looking for index of objec
+     * \param value
+     * \return
+     */
+    int indexOf(T* value) const {
+        return m_container.indexOf(value);
+    }
+
+    /*!
+     * \brief findByProperty method finds item in internal container property of that is provided value
+     * \param propertyName Latin1 name of looking property
+     * \param value property of corresponded type inside QVariant container
+     * \return
+     */
+    QSharedPointer<T> findByProperty(const char* propertyName, const QVariant& value) const {
+        auto iter = std::find_if(m_container.begin(), m_container.end(), [=](const QSharedPointer<T> &item) -> bool {
+            return item->property(propertyName) == value;
+        });
+
+        if(iter != m_container.end()) {
+            return *iter;
+        }
+        return QSharedPointer<T>();
+    }
+
+    /*!
+     * \brief container returns internal container
+     * \return
+     */
+    QList<QSharedPointer<T> > container() const {
+        return m_container;
+    }
+
+protected:
+    void clear() {
+        m_container.clear();
+        emit countChanged();
+    }
+
+    QList<QSharedPointer<T> > m_container;
+    static QHash<int, QByteArray> s_roleNames;
+
+
+private:
+    static QByteArray fullTemplateName() { //Debug helper
+        return QString("UniversalListModel<%1>").arg(T::staticMetaObject.className()).toLatin1();
+    }
+};
+
+template<typename T>
+QHash<int, QByteArray> UniversalListModel<T>::s_roleNames;

+ 6 - 0
examples/simplechat/universallistmodelbase.cpp

@@ -0,0 +1,6 @@
+#include "universallistmodelbase.h"
+
+UniversalListModelBase::UniversalListModelBase(QObject *parent) : QAbstractListModel(parent)
+{
+
+}

+ 24 - 0
examples/simplechat/universallistmodelbase.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <QAbstractListModel>
+/*!
+ * \brief The UniversalListModelBase class to make prossible properties definition for UniversalListModel
+ * This class should not be used as is, but leaves this possibility.
+ */
+class UniversalListModelBase : public QAbstractListModel
+{
+    Q_OBJECT
+    Q_PROPERTY(int count READ count NOTIFY countChanged)
+public:
+    explicit UniversalListModelBase(QObject *parent = nullptr);
+
+    /*!
+     * \brief count property that declares row count of UniversalListModel
+     * \return
+     */
+    virtual int count() const = 0;
+    Q_INVOKABLE virtual void remove(int valueIndex) = 0;
+
+signals:
+    void countChanged();
+};

+ 25 - 0
examples/simplechatserver/CMakeLists.txt

@@ -0,0 +1,25 @@
+set(TARGET simplechatserver)
+
+set(GENERATED_SOURCES
+    ${CMAKE_CURRENT_BINARY_DIR}/simplechat.pb.cc
+    ${CMAKE_CURRENT_BINARY_DIR}/simplechat.grpc.pb.cc)
+set_source_files_properties(${GENERATED_SOURCES} PROPERTIES GENERATED TRUE)
+
+add_executable(${TARGET} main.cpp ${GENERATED_SOURCES})
+target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+
+file(GLOB PROTO_FILES ABSOLUTE ${CMAKE_CURRENT_SOURCE_DIR}/../simplechat/proto/*.proto)
+protobuf_generate_all(TARGET ${TARGET}
+    GENERATED_SOURCES ${GENERATED_SOURCES}
+    PROTO_FILES ${PROTO_FILES})
+
+if(WIN32)
+    include_directories(${GRPC_INCLUDE_PATHS} "/")
+    set(GRPC_LIBRARIES "")
+    #Needs to set path to protobuf libraries
+    set(PROTOBUF_LIBRARIES_PATH ${PROTOBUF_INSTALATION_PATH}/lib)
+    link_directories(${PROTOBUF_LIBRARIES_PATH} ${GRPC_LIBRARIES})
+endif()
+
+target_link_libraries(${TARGET} ${Protobuf_LIBRARIES} grpc++ grpc)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/cert.pem ${CMAKE_CURRENT_SOURCE_DIR}/key.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

+ 34 - 0
examples/simplechatserver/cert.pem

@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF6zCCA9OgAwIBAgIJAMPfLBc9ERZGMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
+VQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEzARBgNV
+BAoMClF0UHJvdG9idWYxDDAKBgNVBAsMA1JuRDESMBAGA1UEAwwJbG9jYWxob3N0
+MSMwIQYJKoZIhvcNAQkBFhRxdHByb3RvYnVmQGdtYWlsLmNvbTAeFw0xOTA0MjMx
+NTI5MzNaFw0yMDA0MjIxNTI5MzNaMIGLMQswCQYDVQQGEwJERTEPMA0GA1UECAwG
+QmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEzARBgNVBAoMClF0UHJvdG9idWYxDDAK
+BgNVBAsMA1JuRDESMBAGA1UEAwwJbG9jYWxob3N0MSMwIQYJKoZIhvcNAQkBFhRx
+dHByb3RvYnVmQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBANCB7zdc6v+gsYCsGIYsT3iyGPgvFG7cJJWnQO9iW6Tn/kBcreYuCdVSSODK
+/NtPaA1r8j9FCDvLkzewNQ2Idv08/oKBYfOaVtQvh1cn7ktr1usiXtYv16cimeKk
+8iYbiczkZOah2xq+ivm9+05WkYTzcSjBpXg19894024GHd7oRV9G5MCr760k8YLM
+ALnoPpTl0yfs5cEcTybqvZFZNqkHDX2ziEbcF/mVcxcyEsmenbX6MI0easg2qZeq
+Sb7AW7tIMVoWUxDkUIor4vogbgU2IljAjzn0i+fPncB41TU5IiIU35vO67kFBXyI
+Ms3LpN1+Siz5HYHjwaWl0ecebuT83kP33VNc1ULkKJG5UbZUYfgZjUwZPgFFzVzQ
+cif0mhYj9s5Nmn6Q/twxeIXOIZAQdLOq625Wwx8bh+mGaV5mtw3wtXJjbceGa1z/
+Pnni4x2B7IfSiOzGLZZNRRHIjUskeONHUHn8YBrLLg5RT+tvPnGDVPh0cKEH2P4F
+cNRPmaqg75siFoJ+m3DlFM952tRcfzJumgbUfEnrL8bxTZPYVoq22qCDm3UGNW8n
+pthv8Y2hemv6V9lF050z2vpL2DtU9RmtkBWx7ipPUdsEOm4e0rdOCk7zo8IAiWMQ
+6o1L23IPTsqaEpqk3b7tT+5dtLkpeomAT8Hz91ChWUb6jrTVAgMBAAGjUDBOMB0G
+A1UdDgQWBBQIb2RjqkeMsUjH9QJ5BYnAtJ/vojAfBgNVHSMEGDAWgBQIb2RjqkeM
+sUjH9QJ5BYnAtJ/vojAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQDG
+2x+I9C+4XFCpg9KVA+BBi6+7yi4ulH0yv7W3hNWBV2MxTpvVN6RIt8xI1sPFXc2T
+g2wCIAIPGIPNDe8hO3gcfIIe059P+J3ZNjybUg5p6CR9O7kqgqV/nL4gVRuBJeYq
+6YLr1lKL9r3Zdbkq1tcaCaj816zdXVky0us72XP28/xJc96crgKzDI69vESy5jq6
+QT/HoBwYiSaWXNgb0zJzc//e0upXSLeTkYizAJ5OGkQ/MQYE7gDvtPlGVhQ0rnl/
+FLsiZJokuxtLOTvYJ8Gynjz3QwbClN/bmUbOgD0fqW6BEZyZSJ4zCz0BJnwg46gc
+IN+p6vi50MG4ZcJcnMl/3tAxt2RxHNLi0j21NSLyFj60gK+vL3/zzkdYF9ZxX1L+
+dqhTqioCVrV96rJIQbz6JrbFhUCdyEHYG7yi6LxHTUex33XouhGAfZ0lri5wWZq8
+0Rx8PEZ7SbjARtnvA7uXIAfgD+n3oqnkg9IDPH8PbdRcJrAdje3O2c1+OzTAC8ni
+czaCWG748gfZPe0JpENP7P56RTh9avj4sHISCx4r+sh0lruLp2JZPr9qtw09uWlq
+Mn58bcnDu9RjVfPXk43s9WfJC9XII+JoNcW3iJDSHxQb9XLvwR9ntWhkVvOKenPP
+b00kWr6FfId6fiOmUww5WkW+Wtt4XOYsypaN+DskeA==
+-----END CERTIFICATE-----

+ 52 - 0
examples/simplechatserver/key.pem

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+0mVZG9D69wbsZefVDJii9NLvWThGog==
+-----END PRIVATE KEY-----

+ 165 - 0
examples/simplechatserver/main.cpp

@@ -0,0 +1,165 @@
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <thread>
+#include <tuple>
+
+#include <grpc++/grpc++.h>
+#include <simplechat.pb.h>
+#include <simplechat.grpc.pb.h>
+using namespace ::qtprotobuf::examples;
+
+class MessageListHandler;
+class UserListHandler;
+
+class SimpleChatService final : public SimpleChat::WithAsyncMethod_messageList<SimpleChat::WithAsyncMethod_usersOnline<SimpleChat::Service>> {
+public:
+    Users m_usersOnline;
+    Users m_usersDatabase;
+    ChatMessages m_messages;
+
+    SimpleChatService() {
+        User *newUser = m_usersDatabase.add_users();
+        newUser->set_name("user1");
+        newUser->set_password("d8578edf8458ce06fbc5bb76a58c5ca4");
+        newUser = m_usersDatabase.add_users();
+        newUser->set_name("user2");
+        newUser->set_password("d8578edf8458ce06fbc5bb76a58c5ca4");
+        newUser = m_usersDatabase.add_users();
+        newUser->set_name("user3");
+        newUser->set_password("d8578edf8458ce06fbc5bb76a58c5ca4");
+        newUser = m_usersDatabase.add_users();
+        newUser->set_name("user4");
+        newUser->set_password("d8578edf8458ce06fbc5bb76a58c5ca4");
+        newUser = m_usersDatabase.add_users();
+        newUser->set_name("user5");
+        newUser->set_password("d8578edf8458ce06fbc5bb76a58c5ca4");
+    }
+
+    void loginUser(MessageListHandler *userHandler);
+
+    void updateActiveUsers() {
+        for(unsigned int i = 0; i < (m_activeUserListClients.size() - 1); i++) {
+            m_activeUserListClients[i]->Write(m_usersOnline, nullptr);
+        }
+    }
+
+    void updateMessages() {
+        for(unsigned int i = 0; i < (m_activeClients.size() - 1); i++) {
+            m_activeClients[i]->Write(m_messages, nullptr);
+        }
+    }
+
+    bool checkUserCredentials(const std::string &name, const std::string &password) {
+        for(int i = 0; i < m_usersDatabase.users_size(); i++) {
+            if (m_usersDatabase.users(i).name() == name && m_usersDatabase.users(i).password() == password) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    std::tuple<std::string, std::string> extractCredentials(grpc::ServerContext *context) {
+        std::string name{};
+        std::string password{};
+        for (auto it = context->client_metadata().begin(); it != context->client_metadata().end(); ++it) {
+            if ((*it).first == std::string("user-name")) {
+                name = std::string((*it).second.data());
+            }
+            if ((*it).first == std::string("user-password")) {
+                password = std::string((*it).second.data());
+            }
+        }
+        return std::make_tuple(name, password);
+    }
+
+    ::grpc::Status sendMessage(grpc::ServerContext *context, const ChatMessage *request, None *) override
+    {
+        std::string name{};
+        std::string password{};
+        std::tie(name, password) = extractCredentials(context);
+        if (!checkUserCredentials(name, password)) {
+            return ::grpc::Status(::grpc::StatusCode::UNAUTHENTICATED, "User or login are invalid");
+        }
+
+        ChatMessage *msg = m_messages.add_messages();
+        *msg = *request;
+        msg->set_from(name);
+        updateMessages();
+        return ::grpc::Status();
+    }
+
+    std::vector<::grpc::ServerAsyncWriter<ChatMessages> *> m_activeClients;
+    std::vector<::grpc::ServerAsyncWriter<Users> *> m_activeUserListClients;
+};
+
+class MessageListHandler {
+public:
+    MessageListHandler(SimpleChatService* service, ::grpc::ServerCompletionQueue* cq) :  tag_(0xdeadbeef)
+      , writer_(&ctx_)
+      , cq_(cq)
+    {
+        service->RequestmessageList(&ctx_, &request_, &writer_, cq_, cq_, &tag_);
+        service->loginUser(this);
+    }
+    int tag_;
+    grpc::ServerContext ctx_;
+    None request_;
+    ::grpc::ServerAsyncWriter< ::qtprotobuf::examples::ChatMessages> writer_;
+    ::grpc::ServerCompletionQueue* cq_;
+};
+
+void SimpleChatService::loginUser(MessageListHandler *handler) {
+    m_activeClients.push_back(&(handler->writer_));
+    //TODO: update online if required here
+}
+
+int main(int argc, char *argv[])
+{
+    std::string server_address("localhost:65002");
+    SimpleChatService service;
+
+    std::ifstream tfile("cert.pem");
+    std::stringstream cert;
+    cert << tfile.rdbuf();
+    tfile.close();
+
+    tfile.open("key.pem");
+    std::stringstream key;
+    key << tfile.rdbuf();
+    tfile.close();
+
+    grpc::ServerBuilder builder;
+    grpc::SslServerCredentialsOptions opts(GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE);
+    opts.pem_key_cert_pairs.push_back({key.str(), cert.str()});
+    builder.AddListeningPort(server_address, grpc::SslServerCredentials(opts));
+    builder.RegisterService(&service);
+    std::unique_ptr<grpc::ServerCompletionQueue> cq = builder.AddCompletionQueue();
+    std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+    std::cout << "Server listening on " << server_address << std::endl;
+    MessageListHandler *last = new MessageListHandler(&service, cq.get());
+    while (true) {
+        unsigned int *tag;
+        bool ok;
+        cq->Next((void**)&tag, &ok);
+        if (tag == nullptr) {
+            continue;
+        }
+        if ((*tag) == 0xdeadbeef) {
+            std::string name{};
+            std::string password{};
+            std::tie(name, password) = service.extractCredentials(&(last->ctx_));
+            if (!service.checkUserCredentials(name, password)) {
+                std::cout << "Authentication failed" << std::endl;
+                last->writer_.Finish(::grpc::Status(::grpc::StatusCode::UNAUTHENTICATED, "User or login are invalid"), nullptr);
+                last = new MessageListHandler(&service, cq.get());
+                continue;
+            }
+            std::cout << "Authentication ok update user chat" << std::endl;
+            last->writer_.Write(service.m_messages, nullptr);
+            last = new MessageListHandler(&service, cq.get());
+        }
+    }
+}