Рефакторинг библиотеки для Redis

This commit is contained in:
Andrei Astafev 2020-04-23 12:10:15 +03:00
parent aa1f06b306
commit 8d9a5590d7
18 changed files with 429 additions and 358 deletions

View File

@ -44,7 +44,7 @@ add_subdirectory(src/myx/base)
add_subdirectory(src/myx/filesystem) add_subdirectory(src/myx/filesystem)
add_subdirectory(src/myx/qt) add_subdirectory(src/myx/qt)
add_subdirectory(src/myx/math) add_subdirectory(src/myx/math)
# add_subdirectory(src/myx/redis) add_subdirectory(src/myx/redis)
# Примеры # Примеры
if(MYXLIB_BUILD_EXAMPLES OR MYXLIB_BUILD_EXAMPLES_HO) if(MYXLIB_BUILD_EXAMPLES OR MYXLIB_BUILD_EXAMPLES_HO)

View File

@ -5,7 +5,6 @@ set(TRGT redis)
# Список файлов исходных текстов # Список файлов исходных текстов
set(TRGT_cpp set(TRGT_cpp
${CMAKE_CURRENT_SOURCE_DIR}/client.cpp ${CMAKE_CURRENT_SOURCE_DIR}/client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/config.cpp
${CMAKE_CURRENT_SOURCE_DIR}/lexer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/request.cpp) ${CMAKE_CURRENT_SOURCE_DIR}/request.cpp)
@ -13,13 +12,18 @@ set(TRGT_cpp
# Список заголовочных файлов (используется для установки) # Список заголовочных файлов (используется для установки)
set(TRGT_moc_hpp set(TRGT_moc_hpp
${CMAKE_CURRENT_SOURCE_DIR}/client.hpp ${CMAKE_CURRENT_SOURCE_DIR}/client.hpp
${CMAKE_CURRENT_SOURCE_DIR}/config.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lexer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/parser.hpp ${CMAKE_CURRENT_SOURCE_DIR}/parser.hpp
${CMAKE_CURRENT_SOURCE_DIR}/reply.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reply.hpp
${CMAKE_CURRENT_SOURCE_DIR}/request.hpp) ${CMAKE_CURRENT_SOURCE_DIR}/request.hpp)
set(TRGT_headers ${TRGT_hpp}) set(TRGT_hpp
${CMAKE_CURRENT_SOURCE_DIR}/client-inl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lexer-inl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/parser-inl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/request-inl.hpp)
set(TRGT_headers ${TRGT_moc_hpp} ${TRGT_hpp})
# cmake-format: on # cmake-format: on
add_library(${TRGT}-header-only INTERFACE) add_library(${TRGT}-header-only INTERFACE)

View File

@ -0,0 +1,82 @@
#ifndef MYX_REDIS_CLIENT_INL_HPP_
#define MYX_REDIS_CLIENT_INL_HPP_
#pragma once
#ifndef MYXLIB_HEADER_ONLY
#include <myx/redis/client.hpp>
#endif
#include <myx/redis/client_p.hpp>
namespace myx {
namespace redis {
ClientPrivate::ClientPrivate( Client* client ) :
lexer ( &socket ),
parser( &lexer )
{
connect( &socket, &QTcpSocket::connected, client, &Client::connected );
connect( &socket, &QTcpSocket::disconnected, client, &Client::disconnected );
connect( &parser, &Parser::reply, this, &ClientPrivate::sendReply );
}
void ClientPrivate::sendReply( const Reply& reply )
{
Q_EMIT queue.dequeue()->reply( reply );
}
Client::Client( QObject* parent ) :
QObject( parent ),
d ( new ClientPrivate( this ) )
{
}
void Client::connectToHost( const QString& hostName, quint16 port )
{
d->socket.connectToHost( hostName, port );
}
void Client::disconnectFromHost()
{
d->socket.disconnectFromHost();
}
bool Client::isConnected() const
{
return( d->socket.state() == QAbstractSocket::ConnectedState );
}
Request* Client::sendCommand( const QByteArray& command )
{
d->socket.write( command + "\r\n" );
auto* request = new Request( this );
d->queue.enqueue( request );
return( request );
}
bool Client::waitForConnected( int msecs )
{
return( d->socket.waitForConnected( msecs ) );
}
bool Client::waitForDisconnected( int msecs )
{
return( d->socket.waitForDisconnected( msecs ) );
}
} // namespace redis
} // namespace myx
#endif // ifndef MYX_REDIS_CLIENT_INL_HPP_

View File

@ -1,72 +1,5 @@
#include <client.hpp> #ifndef MYXLIB_BUILD_LIBRARIES
#include <client_p.hpp> #error Define MYXLIB_BUILD_LIBRARIES to compile this file.
#endif
namespace myx { #include <myx/redis/client-inl.hpp>
namespace redis {
ClientPrivate::ClientPrivate( Client* client ) :
lexer ( &socket ),
parser( &lexer )
{
connect( &socket, &QTcpSocket::connected, client, &Client::connected );
connect( &socket, &QTcpSocket::disconnected, client, &Client::disconnected );
connect( &parser, &Parser::reply, this, &ClientPrivate::sendReply );
}
void ClientPrivate::sendReply( const Reply& reply )
{
Q_EMIT queue.dequeue()->reply( reply );
}
Client::Client( QObject* parent ) :
QObject( parent ),
d ( new ClientPrivate( this ) )
{
}
void Client::connectToHost( const QString& hostName, quint16 port )
{
d->socket.connectToHost( hostName, port );
}
void Client::disconnectFromHost()
{
d->socket.disconnectFromHost();
}
bool Client::isConnected() const
{
return( d->socket.state() == QAbstractSocket::ConnectedState );
}
Request* Client::sendCommand( const QByteArray& command )
{
d->socket.write( command + "\r\n" );
auto* request = new Request( this );
d->queue.enqueue( request );
return( request );
}
bool Client::waitForConnected( int msecs )
{
return( d->socket.waitForConnected( msecs ) );
}
bool Client::waitForDisconnected( int msecs )
{
return( d->socket.waitForDisconnected( msecs ) );
}
} // namespace redis
} // namespace myx

View File

@ -1,22 +1,24 @@
#ifndef MYX_REDIS_CLIENT_HPP_ #ifndef MYX_REDIS_CLIENT_HPP_
#define MYX_REDIS_CLIENT_HPP_ #define MYX_REDIS_CLIENT_HPP_
#pragma once
#include <myx/base/config.hpp>
#include <myx/redis/request.hpp>
#include <QObject> #include <QObject>
#include <QScopedPointer> #include <QScopedPointer>
#include <config.hpp>
#include <request.hpp>
namespace myx { namespace myx {
namespace redis { namespace redis {
class MYX_REDIS_EXPORT ClientPrivate; QT_FORWARD_DECLARE_CLASS( ClientPrivate )
/** /**
* @brief Provides access to a Redis server * @brief Provides access to a Redis server
*/ */
class MYX_REDIS_EXPORT Client : public QObject class Client : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -104,10 +106,15 @@ public:
private: private:
const QScopedPointer< ClientPrivate > d; const QScopedPointer< ClientPrivate > d;
}; // class MYX_REDIS_EXPORT }; // class Client
} // namespace redis } // namespace redis
} // namespace myx } // namespace myx
#ifdef MYXLIB_HEADER_ONLY
#include "client-inl.hpp"
#endif
#endif // MYX_REDIS_CLIENT_HPP_ #endif // MYX_REDIS_CLIENT_HPP_

View File

@ -1 +0,0 @@
#include "config.hpp"

View File

@ -1,14 +0,0 @@
#ifndef MYX_REDIS_CONFIG_HPP_
#define MYX_REDIS_CONFIG_HPP_
#include <QtCore/qglobal.h>
// #if defined( qredis_EXPORTS )
#define MYX_REDIS_EXPORT Q_DECL_EXPORT
// #else
// #define MYX_REDIS_EXPORT Q_DECL_IMPORT
// #endif
// #include <myx/base/config_flags.hpp>
#endif // MYX_REDIS_CONFIG_HPP_

View File

@ -1,6 +0,0 @@
#ifndef @CMLIB_PROJECT_NAME_CANONICAL@_CONFIG_FLAGS_HPP_
#define @CMLIB_PROJECT_NAME_CANONICAL@_CONFIG_FLAGS_HPP_
// #cmakedefine
#endif /* @CMLIB_PROJECT_NAME_CANONICAL@_CONFIG_FLAGS_HPP_ */

130
src/myx/redis/lexer-inl.hpp Normal file
View File

@ -0,0 +1,130 @@
#ifndef MYX_REDIS_LEXER_INL_HPP_
#define MYX_REDIS_LEXER_INL_HPP_
#pragma once
#ifndef MYXLIB_HEADER_ONLY
#include <myx/redis/lexer.hpp>
#endif
namespace myx {
namespace redis {
Lexer::Lexer( QIODevice* device, QObject* parent ) :
QObject ( parent ),
m_device( device ),
m_state ( DoingNothing ),
m_crlf ( 0 ),
m_length( 0 )
{
connect( device, &QIODevice::readyRead, this, &Lexer::readData );
}
void Lexer::readData()
{
m_buffer.append( m_device->readAll() );
while ( true )
{
if ( ( m_state == DoingNothing ) && !readCharacter() )
{
break;
}
switch ( m_state )
{
case ReadingLength:
case ReadingUnsafeString:
if ( !readUnsafeString() ) { return; }
break;
case ReadingSafeString:
if ( !readSafeString() ) { return; }
break;
case DoingNothing:
break;
}
if ( m_state != ReadingSafeString )
{
m_state = DoingNothing;
}
}
} // Lexer::readData
bool Lexer::readCharacter()
{
if ( m_buffer.isEmpty() )
{
return( false );
}
char c = m_buffer.at( 0 );
m_buffer.remove( 0, 1 );
switch ( c )
{
case '+':
case '-':
case ':':
case '*':
m_state = ReadingUnsafeString; break;
case '$':
m_state = ReadingLength; break;
}
Q_EMIT character( c );
return( true );
} // Lexer::readCharacter
bool Lexer::readUnsafeString()
{
m_crlf = m_buffer.indexOf( "\r\n", m_crlf );
if ( m_crlf == -1 )
{
m_crlf = m_buffer.size();
return( false );
}
QString s = m_buffer.mid( 0, m_crlf );
m_buffer.remove( 0, m_crlf + 2 );
if ( m_state == ReadingLength )
{
m_length = s.toInt();
m_state = ReadingSafeString;
}
else
{
Q_EMIT unsafeString( s );
}
m_crlf = 0;
return( true );
} // Lexer::readUnsafeString
bool Lexer::readSafeString()
{
if ( m_buffer.size() - m_length < 2 )
{
return( false );
}
QByteArray d = m_buffer.mid( 0, m_length );
m_buffer.remove( 0, m_length + 2 );
Q_EMIT safeString( d );
m_state = DoingNothing;
return( true );
}
} // namespace redis
} // namespace myx
#endif // ifndef MYX_REDIS_LEXER_INL_HPP_

View File

@ -1,121 +1,5 @@
#include "lexer.hpp" #ifndef MYXLIB_BUILD_LIBRARIES
#error Define MYXLIB_BUILD_LIBRARIES to compile this file.
#endif
namespace myx { #include <myx/redis/lexer-inl.hpp>
namespace redis {
Lexer::Lexer( QIODevice* device, QObject* parent ) :
QObject ( parent ),
m_device( device ),
m_state ( DoingNothing ),
m_crlf ( 0 ),
m_length( 0 )
{
connect( device, &QIODevice::readyRead, this, &Lexer::readData );
}
void Lexer::readData()
{
m_buffer.append( m_device->readAll() );
while ( true )
{
if ( ( m_state == DoingNothing ) && !readCharacter() )
{
break;
}
switch ( m_state )
{
case ReadingLength:
case ReadingUnsafeString:
if ( !readUnsafeString() ) { return; }
break;
case ReadingSafeString:
if ( !readSafeString() ) { return; }
break;
case DoingNothing:
break;
}
if ( m_state != ReadingSafeString )
{
m_state = DoingNothing;
}
}
} // Lexer::readData
bool Lexer::readCharacter()
{
if ( m_buffer.isEmpty() )
{
return( false );
}
char c = m_buffer.at( 0 );
m_buffer.remove( 0, 1 );
switch ( c )
{
case '+':
case '-':
case ':':
case '*':
m_state = ReadingUnsafeString; break;
case '$':
m_state = ReadingLength; break;
}
Q_EMIT character( c );
return( true );
} // Lexer::readCharacter
bool Lexer::readUnsafeString()
{
m_crlf = m_buffer.indexOf( "\r\n", m_crlf );
if ( m_crlf == -1 )
{
m_crlf = m_buffer.size();
return( false );
}
QString s = m_buffer.mid( 0, m_crlf );
m_buffer.remove( 0, m_crlf + 2 );
if ( m_state == ReadingLength )
{
m_length = s.toInt();
m_state = ReadingSafeString;
}
else
{
Q_EMIT unsafeString( s );
}
m_crlf = 0;
return( true );
} // Lexer::readUnsafeString
bool Lexer::readSafeString()
{
if ( m_buffer.size() - m_length < 2 )
{
return( false );
}
QByteArray d = m_buffer.mid( 0, m_length );
m_buffer.remove( 0, m_length + 2 );
Q_EMIT safeString( d );
m_state = DoingNothing;
return( true );
}
} // namespace redis
} // namespace myx

View File

@ -1,8 +1,11 @@
#ifndef MYX_REDIS_LEXER_HPP_ #ifndef MYX_REDIS_LEXER_HPP_
#define MYX_REDIS_LEXER_HPP_ #define MYX_REDIS_LEXER_HPP_
#pragma once
#include <myx/base/config.hpp>
#include <QIODevice> #include <QIODevice>
#include <QObject>
namespace myx { namespace myx {
@ -51,4 +54,9 @@ private:
} // namespace myx } // namespace myx
#ifdef MYXLIB_HEADER_ONLY
#include "lexer-inl.hpp"
#endif
#endif // MYX_REDIS_LEXER_HPP_ #endif // MYX_REDIS_LEXER_HPP_

View File

@ -0,0 +1,91 @@
#ifndef MYX_REDIS_PARSER_INL_HPP_
#define MYX_REDIS_PARSER_INL_HPP_
#pragma once
#ifndef MYXLIB_HEADER_ONLY
#include <myx/redis/parser.hpp>
#endif
namespace myx {
namespace redis {
Parser::Parser( Lexer* lexer, QObject* parent ) :
QObject( parent )
{
connect( lexer, &Lexer::character, this, &Parser::readCharacter );
connect( lexer, &Lexer::unsafeString, this, &Parser::readUnsafeString );
connect( lexer, &Lexer::safeString, this, &Parser::readSafeString );
}
void Parser::readCharacter( const char c )
{
switch ( c )
{
case '+':
stack.append( Task( Reply::Status ) ); break;
case '-':
stack.append( Task( Reply::Error ) ); break;
case ':':
stack.append( Task( Reply::Integer ) ); break;
case '$':
stack.append( Task( Reply::Bulk ) ); break;
case '*':
stack.append( Task( Reply::MultiBulk ) ); break;
default:
break;
}
}
void Parser::readUnsafeString( const QString& value )
{
if ( tos().reply.type() == Reply::MultiBulk )
{
tos().count = value.toInt();
}
else
{
tos().reply.value() = value;
}
descend();
}
void Parser::readSafeString( const QByteArray& value )
{
tos().reply.value() = value;
descend();
}
void Parser::descend()
{
while ( true )
{
if ( ( tos().reply.type() == Reply::MultiBulk ) &&
( tos().reply.value().toList().count() < tos().count ) )
{
return;
}
if ( stack.count() == 1 )
{
auto r = stack.takeLast().reply;
Q_EMIT reply( r );
return;
}
auto r = stack.takeLast().reply;
tos().reply.value().toList().append( QVariant::fromValue( r ) );
}
}
} // namespace redis
} // namespace myx
#endif // ifndef MYX_REDIS_PARSER_INL_HPP_

View File

@ -1,82 +1,5 @@
#include "parser.hpp" #ifndef MYXLIB_BUILD_LIBRARIES
#error Define MYXLIB_BUILD_LIBRARIES to compile this file.
#endif
namespace myx { #include <myx/redis/parser-inl.hpp>
namespace redis {
Parser::Parser( Lexer* lexer, QObject* parent ) :
QObject( parent )
{
connect( lexer, &Lexer::character, this, &Parser::readCharacter );
connect( lexer, &Lexer::unsafeString, this, &Parser::readUnsafeString );
connect( lexer, &Lexer::safeString, this, &Parser::readSafeString );
}
void Parser::readCharacter( const char c )
{
switch ( c )
{
case '+':
stack.append( Task( Reply::Status ) ); break;
case '-':
stack.append( Task( Reply::Error ) ); break;
case ':':
stack.append( Task( Reply::Integer ) ); break;
case '$':
stack.append( Task( Reply::Bulk ) ); break;
case '*':
stack.append( Task( Reply::MultiBulk ) ); break;
default:
break;
}
}
void Parser::readUnsafeString( const QString& value )
{
if ( tos().reply.type() == Reply::MultiBulk )
{
tos().count = value.toInt();
}
else
{
tos().reply.value() = value;
}
descend();
}
void Parser::readSafeString( const QByteArray& value )
{
tos().reply.value() = value;
descend();
}
void Parser::descend()
{
while ( true )
{
if ( ( tos().reply.type() == Reply::MultiBulk ) &&
( tos().reply.value().toList().count() < tos().count ) )
{
return;
}
if ( stack.count() == 1 )
{
auto r = stack.takeLast().reply;
Q_EMIT reply( r );
return;
}
auto r = stack.takeLast().reply;
tos().reply.value().toList().append( QVariant::fromValue( r ) );
}
}
} // namespace redis
} // namespace myx

View File

@ -1,14 +1,16 @@
#ifndef MYX_REDIS_PARSER_HPP_ #ifndef MYX_REDIS_PARSER_HPP_
#define MYX_REDIS_PARSER_HPP_ #define MYX_REDIS_PARSER_HPP_
#pragma once
#include <myx/base/config.hpp>
#include <myx/redis/reply.hpp>
#include <myx/redis/lexer.hpp>
#include <QList> #include <QList>
#include <QObject>
#include <QPair> #include <QPair>
#include <QVariant> #include <QVariant>
#include <reply.hpp>
#include <lexer.hpp>
namespace myx { namespace myx {
namespace redis { namespace redis {
@ -54,4 +56,8 @@ public:
} // namespace myx } // namespace myx
#ifdef MYXLIB_HEADER_ONLY
#include "parser-inl.hpp"
#endif
#endif // MYX_REDIS_PARSER_HPP_ #endif // MYX_REDIS_PARSER_HPP_

View File

@ -1,9 +1,11 @@
#ifndef MYX_REDIS_REPLY_HPP_ #ifndef MYX_REDIS_REPLY_HPP_
#define MYX_REDIS_REPLY_HPP_ #define MYX_REDIS_REPLY_HPP_
#include <QVariant> #pragma once
#include <config.hpp> #include <myx/base/config.hpp>
#include <QVariant>
namespace myx { namespace myx {
@ -12,7 +14,7 @@ namespace redis {
/** /**
* @brief Represents a Redis reply * @brief Represents a Redis reply
*/ */
class MYX_REDIS_EXPORT Reply class Reply
{ {
public: public:

View File

@ -0,0 +1,53 @@
#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 <myx/redis/request_p.hpp>
#include <QTimer>
namespace myx {
namespace redis {
void RequestPrivate::quitEventLoop()
{
loop.exit( 1 );
}
Request::Request( QObject* parent ) :
QObject( parent ),
d ( new RequestPrivate )
{
connect( this, &Request::reply, this, &Request::deleteLater );
}
bool Request::waitForReply( int msecs )
{
QTimer timer;
timer.setInterval( msecs );
timer.setSingleShot( true );
connect( &timer, &QTimer::timeout, &d->loop, &QEventLoop::quit );
connect( this, &Request::reply, d.data(), &RequestPrivate::quitEventLoop );
/*
* If the timer fires, the return value will be 0.
* Otherwise, quitEventLoop() will terminate the loop with 1.
*/
return( ( d->loop.exec( QEventLoop::ExcludeUserInputEvents ) != 0 ) );
}
} // namespace redis
} // namespace myx
#endif // ifndef MYX_REDIS_REQUEST_INL_HPP_

View File

@ -1,42 +1,5 @@
#include <QTimer> #ifndef MYXLIB_BUILD_LIBRARIES
#error Define MYXLIB_BUILD_LIBRARIES to compile this file.
#endif
#include <request.hpp> #include <myx/redis/request-inl.hpp>
#include <request_p.hpp>
namespace myx {
namespace redis {
void RequestPrivate::quitEventLoop()
{
loop.exit( 1 );
}
Request::Request( QObject* parent ) :
QObject( parent ),
d ( new RequestPrivate )
{
connect( this, &Request::reply, this, &Request::deleteLater );
}
bool Request::waitForReply( int msecs )
{
QTimer timer;
timer.setInterval( msecs );
timer.setSingleShot( true );
connect( &timer, &QTimer::timeout, &d->loop, &QEventLoop::quit );
connect( this, &Request::reply, d.data(), &RequestPrivate::quitEventLoop );
/*
* If the timer fires, the return value will be 0.
* Otherwise, quitEventLoop() will terminate the loop with 1.
*/
return( ( d->loop.exec( QEventLoop::ExcludeUserInputEvents ) != 0 ) );
}
} // namespace redis
} // namespace myx

View File

@ -1,22 +1,24 @@
#ifndef MYX_REDIS_REQUEST_HPP_ #ifndef MYX_REDIS_REQUEST_HPP_
#define MYX_REDIS_REQUEST_HPP_ #define MYX_REDIS_REQUEST_HPP_
#pragma once
#include <myx/base/config.hpp>
#include <myx/redis/reply.hpp>
#include <QObject> #include <QObject>
#include <QScopedPointer> #include <QScopedPointer>
#include <config.hpp>
#include <reply.hpp>
namespace myx { namespace myx {
namespace redis { namespace redis {
class MYX_REDIS_EXPORT RequestPrivate; QT_FORWARD_DECLARE_CLASS( RequestPrivate );
/** /**
* @brief Represents a Redis command and its response * @brief Represents a Redis command and its response
*/ */
class MYX_REDIS_EXPORT Request : public QObject class Request : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -49,10 +51,14 @@ public:
private: private:
const QScopedPointer< RequestPrivate > d; const QScopedPointer< RequestPrivate > d;
}; // class MYX_REDIS_EXPORT }; // class Request
} // namespace redis } // namespace redis
} // namespace myx } // namespace myx
#ifdef MYXLIB_HEADER_ONLY
#include "request-inl.hpp"
#endif
#endif // MYX_REDIS_REQUEST_HPP_ #endif // MYX_REDIS_REQUEST_HPP_