15.1.976. tablet_qt/whisker/whiskerworker.h

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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 3 of the License, or
    (at your option) any later version.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once
#include <QAbstractSocket>
#include <QMutex>
#include <QString>
#include <QVector>
#include <QWaitCondition>

#include "whisker/whiskerconnectionstate.h"
#include "whisker/whiskerconstants.h"
#include "whisker/whiskerinboundmessage.h"
#include "whisker/whiskeroutboundcommand.h"
class QTcpSocket;

class WhiskerWorker : public QObject
{
    // Object to manage communication with a Whisker server via TCP/IP.
    // Once created, this object is moved (by WhiskerManager) into a NEW
    // THREAD. Its functionality is then driven by Qt events.

    Q_OBJECT

public:
    // Constructor.
    WhiskerWorker();

    // Wait for an immediate reply to arrive, then return it.
    // Called from OTHER THREADS.
    WhiskerInboundMessage getPendingImmediateReply();

    // Is the main socket connected?
    bool isMainConnected() const;

    // Is the immediate socket connected?
    bool isImmediateConnected() const;

    // Are both sockets connected?
    bool isFullyConnected() const;

    // Are both sockets disconnected?
    bool isFullyDisconnected() const;

signals:
    // "The connection state has changed." (See WhiskerConnectionState.)
    void connectionStateChanged(WhiskerConnectionState state);

    // "We are now fully connected."
    void onFullyConnected();

    // "Message received on main socket."
    // (Will be connected to WhiskerManager.)
    void receivedFromServerMainSocket(const WhiskerInboundMessage& msg);

    // "A socket error has occurred."
    void socketError(const QString& msg);

public slots:
    // "Please connect to the specified Whisker server."
    void connectToServer(
        const QString& host,
        quint16 main_port = whiskerconstants::WHISKER_DEFAULT_PORT
    );

    // "Disconnect from the Whisker server."
    void disconnectFromServer();

    // "Send this message to the server." (See WhiskerOutboundCommand.)
    void sendToServer(const WhiskerOutboundCommand& cmd);
    // ... from WhiskerManager

protected slots:
    // "The main socket is connected."
    void onMainSocketConnected();

    // "The immediate socket is connected."
    void onImmSocketConnected();

    // "One of our sockets has been disconnected."
    void onAnySocketDisconnected();

    // "Data is ready to be read from the main socket."
    void onDataReadyFromMainSocket();

    // "Data is ready to be read from the immediate socket."
    void onDataReadyFromImmediateSocket();

    // "An error has occurred on the main socket."
    void onMainSocketError(QAbstractSocket::SocketError error);

    // "An error has occurred on the immediate socket."
    void onImmSocketError(QAbstractSocket::SocketError error);

protected:
    // Set the connection state. If we're fully connected, emit
    // onFullyConnected().
    void setConnectionState(WhiskerConnectionState state);

    // Returns all inbound messages currently available for a given socket.
    QVector<WhiskerInboundMessage>
        getIncomingMessagesFromSocket(bool via_immediate_socket);

    // Returns all inbound messages currently available for a given socket.
    // Lower-level function than the other one with the same name.
    QVector<WhiskerInboundMessage> getIncomingMessagesFromBuffer(
        QString& buffer, bool via_immediate_socket, const QDateTime& timestamp
    );

    // Handle the low-level connection messages, and pass anything else on
    // via our signals.
    void processMainSocketMessage(const WhiskerInboundMessage& msg);

    // Push a reply, received from the immediate socket, into our "replies
    // awaiting collection" queue.
    void pushImmediateReply(WhiskerInboundMessage& msg);

protected:
    quint16 m_main_port;  // main port number
    quint16 m_imm_port;  // immediate port number
    QString m_host;  // hostname
    QString m_code;  // security code given to us by the server
    QTcpSocket* m_main_socket;  // main socket
    QTcpSocket* m_immediate_socket;  // immediate socket
    WhiskerConnectionState m_connection_state;  // overall connection status
    QString m_inbound_buffer_main;  // inbound message buffer for main socket
    QString m_inbound_buffer_imm;
    // ... inbound message buffer for immediate socket
    QVector<WhiskerOutboundCommand> m_imm_commands_awaiting_reply;
    // ... outbound commands waiting to be matched to a reply
    QVector<WhiskerInboundMessage> m_imm_replies_awaiting_collection;
    // ... inbound replies, already matched with the outbound command that
    //     triggered them, now awaiting collection
    QMutex m_mutex_imm;
    // ... mutex for m_imm_replies_awaiting_collection AND
    //     m_imm_commands_awaiting_reply
    QWaitCondition m_immediate_reply_arrived;
    // ... "a reply has arrived"
};