Закончена основа библиотеки для работы с Redis

This commit is contained in:
Andrei Astafev 2020-04-24 17:13:33 +03:00
parent 8ff24d264c
commit b16bab5fed
10 changed files with 133 additions and 114 deletions

View File

@ -30,7 +30,7 @@ if(MYXLIB_BUILD_EXAMPLES)
add_pvs_check(${TRGT}) add_pvs_check(${TRGT})
# Создание цели для автоматического форматирования кода # Создание цели для автоматического форматирования кода
add_format_sources(${TRGT} ${TRGT_cpp}) add_format_sources(${TRGT} ${TRGT_cpp} ${TRGT_moc_hpp})
# Qt5 # Qt5
target_include_directories(${TRGT} PRIVATE ${CMAKE_SOURCE_DIR}/src) target_include_directories(${TRGT} PRIVATE ${CMAKE_SOURCE_DIR}/src)
@ -60,10 +60,7 @@ endif()
if(MYXLIB_BUILD_EXAMPLES_HO) if(MYXLIB_BUILD_EXAMPLES_HO)
set(REDIS_LIB_DIR ${CMAKE_SOURCE_DIR}/src/myx/redis) set(REDIS_LIB_DIR ${CMAKE_SOURCE_DIR}/src/myx/redis)
set(REDIS_moc_hpp set(REDIS_moc_hpp ${REDIS_LIB_DIR}/client.hpp ${REDIS_LIB_DIR}/lexer.hpp ${REDIS_LIB_DIR}/parser.hpp
${REDIS_LIB_DIR}/client.hpp
${REDIS_LIB_DIR}/lexer.hpp
${REDIS_LIB_DIR}/parser.hpp
${REDIS_LIB_DIR}/request.hpp) ${REDIS_LIB_DIR}/request.hpp)
qt5_wrap_cpp(REDIS_moc_cpp ${REDIS_moc_hpp}) qt5_wrap_cpp(REDIS_moc_cpp ${REDIS_moc_hpp})

View File

@ -10,60 +10,80 @@ class RedisClient : public QObject
Q_OBJECT Q_OBJECT
MR::Client m_client; MR::Client m_client;
MR::Client m_subscribe;
MR::Request* m_request; MR::Request* m_request;
MR::Request* m_channel;
public: public:
RedisClient(QObject* parent = nullptr) : QObject(parent) RedisClient( QObject* parent = nullptr ) :
QObject( parent )
{ {
connect(&m_client, &MR::Client::connected, this, &RedisClient::slotConnected); connect( &m_client, &MR::Client::connected, this, &RedisClient::slotConnected );
m_client.connectToHost("127.0.0.1"); connect( &m_subscribe, &MR::Client::connected, this, &RedisClient::slotStartSubscribe );
m_client.connectToHost( "127.0.0.1" );
m_subscribe.connectToHost( "127.0.0.1" );
} }
virtual ~RedisClient() {} virtual ~RedisClient() {}
Q_SLOT void slotStartSubscribe()
{
m_channel = m_subscribe.subscribeToChannel( "test" );
connect( m_channel, &MR::Request::reply, this, &RedisClient::slotSubscribeTest );
}
Q_SLOT void slotSubscribeTest( MR::Reply reply )
{
qDebug() << static_cast< int >( reply.type() );
auto& v = reply.value();
if ( !v.canConvert< QVariantList >() ) { return; }
auto l = v.toList();
for ( auto& a: l )
{
qDebug() << a.value< MR::Reply >().value().toByteArray();
}
}
Q_SLOT void slotConnected() Q_SLOT void slotConnected()
{ {
m_request = m_client.sendCommand("PING"); m_request = m_client.sendCommand( "PING" );
connect(m_request, &MR::Request::reply, this, &RedisClient::slotPong); connect( m_request, &MR::Request::reply, this, &RedisClient::slotPong );
} }
Q_SLOT void slotPong( const MR::Reply& reply)
Q_SLOT void slotPong( MR::Reply reply )
{ {
qDebug() << static_cast<int>(reply.type()); qDebug() << static_cast< int >( reply.type() );
qDebug() << const_cast<MR::Reply&>(reply).value().toString(); qDebug() << reply.value().toString();
m_request->disconnect(); m_request->disconnect();
m_request->deleteLater(); m_request->deleteLater();
m_request = m_client.sendCommand( "SET value 10" ); m_request = m_client.sendCommand( "SET value 10" );
connect(m_request, &MR::Request::reply, this, &RedisClient::slotSetValue); connect( m_request, &MR::Request::reply, this, &RedisClient::slotSetValue );
} }
Q_SLOT void slotSetValue( const MR::Reply& reply) Q_SLOT void slotSetValue( MR::Reply reply )
{ {
qDebug() << static_cast<int>(reply.type()); qDebug() << static_cast< int >( reply.type() );
qDebug() << const_cast<MR::Reply&>(reply).value().toString(); qDebug() << reply.value().toString();
m_request->disconnect(); m_request->disconnect();
m_request->deleteLater(); m_request->deleteLater();
m_request = m_client.sendCommand( "GET value" ); m_request = m_client.sendCommand( "GET value" );
connect(m_request, &MR::Request::reply, this, &RedisClient::slotGetValue); connect( m_request, &MR::Request::reply, this, &RedisClient::slotGetValue );
} }
Q_SLOT void slotGetValue( const MR::Reply& reply) Q_SLOT void slotGetValue( MR::Reply reply )
{ {
qDebug() << static_cast<int>(reply.type()); qDebug() << static_cast< int >( reply.type() );
qDebug() << const_cast<MR::Reply&>(reply).value().toByteArray(); qDebug() << reply.value().toByteArray();
m_request->disconnect(); m_request->disconnect();
m_request->deleteLater(); m_request->deleteLater();
m_request = m_client.sendCommand( "SUBSCRIBE test" );
connect(m_request, &MR::Request::reply, this, &RedisClient::slotSubscribeTest);
} }
}; // class RedisClient
Q_SLOT void slotSubscribeTest( const MR::Reply& reply)
{
qDebug() << static_cast<int>(reply.type());
qDebug() << const_cast<MR::Reply&>(reply).value();
}
};

View File

@ -53,7 +53,6 @@ generate_pkgconfig(myx-${TRGT} COMPONENT headers INSTALL_LIBRARY ${MYXLIB_BUILD_
install(FILES ${TRGT_headers} ${CMAKE_BINARY_DIR}/include/myx/base/compiler_features.hpp COMPONENT headers install(FILES ${TRGT_headers} ${CMAKE_BINARY_DIR}/include/myx/base/compiler_features.hpp COMPONENT headers
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${TRGT}) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${TRGT})
# Цель, используемая только для установки заголовочных файлов без компиляции проекта # Цель, используемая только для установки заголовочных файлов без компиляции проекта
add_custom_target(${TRGT}-install-headers COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=headers -P add_custom_target(${TRGT}-install-headers COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=headers -P
"${CMAKE_BINARY_DIR}/cmake_install.cmake") "${CMAKE_BINARY_DIR}/cmake_install.cmake")

View File

@ -46,6 +46,16 @@ MYXLIB_INLINE Request* Client::sendCommand( const QByteArray& command )
} }
MYXLIB_INLINE Request* Client::subscribeToChannel( const QByteArray& channel )
{
d->m_socket.write( "subscribe " + channel + "\r\n" );
auto* request = new Request( channel );
d->m_list.append( request );
return( request );
}
MYXLIB_INLINE bool Client::waitForConnected( int msecs ) MYXLIB_INLINE bool Client::waitForConnected( int msecs )
{ {
return( d->m_socket.waitForConnected( msecs ) ); return( d->m_socket.waitForConnected( msecs ) );

View File

@ -76,6 +76,8 @@ public:
*/ */
Request* sendCommand( const QByteArray& command ); Request* sendCommand( const QByteArray& command );
Request* subscribeToChannel( const QByteArray& command );
/** /**
* @brief Attempts to set the specified key to the specified value * @brief Attempts to set the specified key to the specified value
* @param name the name of the key * @param name the name of the key
@ -128,21 +130,45 @@ class ClientPrivate : public QObject
connect( &m_socket, &QTcpSocket::connected, client, &Client::connected ); connect( &m_socket, &QTcpSocket::connected, client, &Client::connected );
connect( &m_socket, &QTcpSocket::disconnected, client, &Client::disconnected ); connect( &m_socket, &QTcpSocket::disconnected, client, &Client::disconnected );
connect( &m_parser, &Parser::reply, this, &ClientPrivate::sendReply ); connect( &m_parser, &Parser::reply, this, &ClientPrivate::sendReply );
connect( &m_parser, &Parser::replyMultiBulk, this, &ClientPrivate::sendReplyMultiBulk );
} }
QTcpSocket m_socket; QTcpSocket m_socket;
QQueue< Request* > m_queue; QQueue< Request* > m_queue;
QList< Request* > m_list;
Lexer m_lexer; Lexer m_lexer;
Parser m_parser; Parser m_parser;
Q_SLOT void sendReply( const myx::redis::Reply& reply ) Q_SLOT void sendReply( const myx::redis::Reply& reply )
{ {
if ( !m_queue.isEmpty() ) if ( m_queue.isEmpty() ) { return; }
Q_EMIT m_queue.dequeue()->reply( const_cast< myx::redis::Reply& >( reply ) );
} // sendReply
Q_SLOT void sendReplyMultiBulk( const myx::redis::Reply& reply )
{ {
Q_EMIT m_queue.dequeue()->reply( reply ); if ( m_list.isEmpty() ) { return; }
QVariant v = const_cast< myx::redis::Reply& >( reply ).value();
if ( !v.isValid() ) { return; }
if ( !v.canConvert< QVariantList >() ) { return; }
auto l = v.toList();
if ( l.size() >= 2 )
{
auto b = l[1].value< Reply >().value().toByteArray();
for ( auto& request: m_list )
{
if ( request->channel() == b )
{
request->reply( const_cast< myx::redis::Reply& >( reply ) );
} }
} }
}
} // sendReplyMultiBulk
}; // class ClientPrivate }; // class ClientPrivate
} // namespace redis } // namespace redis

View File

@ -70,11 +70,6 @@ MYXLIB_INLINE void Parser::descend()
while ( true ) while ( true )
{ {
auto& task = tos(); auto& task = tos();
if ( task.m_reply.value().isNull() )
{
task.m_reply.value().setValue( QList< QVariant >() );
}
if ( ( task.m_reply.type() == Reply::kMultiBulk ) && if ( ( task.m_reply.type() == Reply::kMultiBulk ) &&
( task.m_reply.value().toList().count() < task.m_count ) ) ( task.m_reply.value().toList().count() < task.m_count ) )
{ {
@ -82,8 +77,15 @@ MYXLIB_INLINE void Parser::descend()
} }
if ( m_stack.count() == 1 ) if ( m_stack.count() == 1 )
{
if ( task.m_reply.type() == Reply::kMultiBulk )
{
Q_EMIT replyMultiBulk( m_stack.takeLast().m_reply );
}
else
{ {
Q_EMIT reply( m_stack.takeLast().m_reply ); Q_EMIT reply( m_stack.takeLast().m_reply );
}
return; return;
} }

View File

@ -30,6 +30,7 @@ public:
~Parser() override = default; ~Parser() override = default;
Q_SIGNAL void reply( const myx::redis::Reply& ); Q_SIGNAL void reply( const myx::redis::Reply& );
Q_SIGNAL void replyMultiBulk( const myx::redis::Reply& );
private: private:
Q_SLOT void readCharacter( char ); Q_SLOT void readCharacter( char );

View File

@ -23,6 +23,14 @@ MYXLIB_INLINE Request::Request( QObject* parent ) :
} }
MYXLIB_INLINE Request::Request( const QByteArray& channel, QObject* parent ) :
QObject( parent ),
d ( new Loop )
{
m_channel = channel;
}
MYXLIB_INLINE bool Request::waitForReply( int msecs ) MYXLIB_INLINE bool Request::waitForReply( int msecs )
{ {
QTimer timer; QTimer timer;

View File

@ -40,6 +40,7 @@ public:
* @param parent the parent QObject * @param parent the parent QObject
*/ */
explicit Request( QObject* parent = nullptr ); explicit Request( QObject* parent = nullptr );
explicit Request( const QByteArray& channel, QObject* parent = nullptr );
Request( const Request& ) = delete; Request( const Request& ) = delete;
Request& operator=( const Request& ) = delete; Request& operator=( const Request& ) = delete;
@ -62,11 +63,12 @@ public:
* @brief Emitted when a reply is received * @brief Emitted when a reply is received
* @param reply the reply received * @param reply the reply received
*/ */
Q_SIGNAL void reply( const myx::redis::Reply& reply ); Q_SIGNAL void reply( myx::redis::Reply& );
QByteArray channel() const { return( m_channel ); }
private: private:
QByteArray m_channel;
const QScopedPointer< Loop > d; const QScopedPointer< Loop > d;
}; // class Request }; // class Request

View File

@ -1,46 +0,0 @@
#ifndef MYX_REDIS_REQUEST_INL_HPP_
#define MYX_REDIS_REQUEST_INL_HPP_
#pragma once
#include <myx/base/config.hpp>
#ifndef MYXLIB_HEADER_ONLY
#include <myx/redis/request.hpp>
#endif
#include <QTimer>
namespace myx {
namespace redis {
MYXLIB_INLINE Request::Request( QObject* parent ) :
QObject( parent ),
d ( new Loop( parent ) )
{
connect( this, &Request::reply, this, &Request::deleteLater );
}
MYXLIB_INLINE bool Request::waitForReply( int msecs )
{
QTimer timer;
timer.setInterval( msecs );
timer.setSingleShot( true );
connect( &timer, &QTimer::timeout, &d->m_loop, &QEventLoop::quit );
connect( this, &Request::reply, d.data(), &Loop::quitEventLoop );
/*
* If the timer fires, the return value will be 0.
* Otherwise, quitEventLoop() will terminate the loop with 1.
*/
return( ( d->m_loop.exec( QEventLoop::ExcludeUserInputEvents ) != 0 ) );
}
} // namespace redis
} // namespace myx
#endif // ifndef MYX_REDIS_REQUEST_INL_HPP_