251 lines
5.6 KiB
C++
251 lines
5.6 KiB
C++
#ifndef MYX_REDIS_CONTAINERS_INL_HPP_
|
|
#define MYX_REDIS_CONTAINERS_INL_HPP_
|
|
|
|
#pragma once
|
|
|
|
#ifndef MYXLIB_HEADER_ONLY
|
|
#include <myx/redis/containers.hpp>
|
|
#endif
|
|
|
|
#include <QDebug>
|
|
#include <QHash>
|
|
|
|
namespace myx {
|
|
|
|
namespace redis {
|
|
|
|
namespace constants {
|
|
|
|
static const QByteArray Separator { "\r\n" }; //!< Строковый разделитель слов во фразе
|
|
|
|
} // namespace constants
|
|
|
|
namespace C = constants;
|
|
|
|
/*!
|
|
* \brief Создаёт часть команды
|
|
* \param value Значение для создании части команды
|
|
* \return Часть команды
|
|
*/
|
|
MYXLIB_INLINE QByteArray part( const QVariant& value )
|
|
{
|
|
auto bytes = value.toByteArray();
|
|
return( "$" + QByteArray::number( bytes.length() ) + C::Separator + bytes );
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief Составляет RESP массив
|
|
* \param parts Части для составления массива
|
|
* \return RESP массив
|
|
*/
|
|
MYXLIB_INLINE QByteArray array( const QVariantList& parts )
|
|
{
|
|
QByteArrayList data;
|
|
std::transform( parts.cbegin(), parts.cend(), std::back_inserter( data ),
|
|
[]( const QVariant& iter ) { return( part( iter ) ); } );
|
|
data.prepend( "*" + QByteArray::number( data.size() ) );
|
|
return( data.join( C::Separator ) + C::Separator );
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief Разбивает буфер данных на массив частей
|
|
* \param buffer Входной буфер данных
|
|
* \param splitLength Длина разобранной части буфера
|
|
* \return Массив частей-строк
|
|
*/
|
|
MYXLIB_INLINE QByteArrayList split( const QByteArray& buffer, int* splitLength )
|
|
{
|
|
enum Token
|
|
{
|
|
kUndefined,
|
|
kFetchNumber,
|
|
kFetchPart,
|
|
};
|
|
|
|
if ( ( buffer == "$-1\r\n" ) || ( buffer == "$*0\r\n" ) || buffer.isEmpty() )
|
|
{
|
|
if ( splitLength != nullptr )
|
|
{
|
|
*splitLength = buffer.length();
|
|
}
|
|
return {};
|
|
}
|
|
if ( buffer.startsWith( "-WRONG" ) )
|
|
{
|
|
if ( splitLength != nullptr )
|
|
{
|
|
*splitLength = buffer.endsWith( "\r\n" ) ? buffer.length() : 0;
|
|
}
|
|
return {};
|
|
}
|
|
if ( splitLength != nullptr )
|
|
{
|
|
*splitLength = 0;
|
|
}
|
|
if ( !buffer.endsWith( C::Separator ) )
|
|
{
|
|
return {};
|
|
}
|
|
int size { 1 };
|
|
int pos { 0 };
|
|
QByteArray partLength;
|
|
if ( buffer[pos] == '*' )
|
|
{
|
|
pos++;
|
|
QByteArray text;
|
|
while ( QChar( buffer[pos] ).isNumber() && pos < buffer.size() )
|
|
{
|
|
text += buffer[pos++];
|
|
}
|
|
if ( pos == buffer.size() )
|
|
{
|
|
return {};
|
|
}
|
|
size = text.toInt();
|
|
pos += C::Separator.length();
|
|
}
|
|
QByteArrayList parts;
|
|
Token token { kUndefined };
|
|
while ( parts.size() < size && pos < buffer.length() )
|
|
{
|
|
switch ( token )
|
|
{
|
|
case kUndefined:
|
|
switch ( buffer[pos] )
|
|
{
|
|
case '$':
|
|
partLength.clear();
|
|
token = kFetchPart;
|
|
pos++;
|
|
break;
|
|
case ':':
|
|
partLength.clear();
|
|
token = kFetchNumber;
|
|
pos++;
|
|
break;
|
|
default:
|
|
return {};
|
|
}
|
|
break;
|
|
case kFetchNumber:
|
|
if ( QChar( buffer[pos] ).isNumber() || ( buffer[pos] == '-' ) )
|
|
{
|
|
partLength += buffer[pos];
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
parts.push_back( partLength );
|
|
token = kUndefined;
|
|
pos += C::Separator.length();
|
|
}
|
|
break;
|
|
case kFetchPart:
|
|
{
|
|
if ( QChar( buffer[pos] ).isNumber() || ( buffer[pos] == '-' ) )
|
|
{
|
|
partLength += buffer[pos];
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
int length = partLength.toInt();
|
|
if ( length < 0 )
|
|
{
|
|
parts.push_back( "" );
|
|
pos += C::Separator.length();
|
|
}
|
|
else
|
|
{
|
|
pos += C::Separator.length();
|
|
if ( pos + length > buffer.length() )
|
|
{
|
|
return {};
|
|
}
|
|
parts.push_back( buffer.mid( pos, length ) );
|
|
pos += length + C::Separator.length();
|
|
}
|
|
token = kUndefined;
|
|
}
|
|
break;
|
|
}
|
|
} // switch
|
|
}
|
|
// может быть ситуация, когда в буфер не дочитан последний \r\n,
|
|
// но все части были вычитаны
|
|
if ( ( parts.size() == size ) && ( pos <= buffer.length() ) )
|
|
{
|
|
if ( splitLength != nullptr )
|
|
{
|
|
*splitLength = pos;
|
|
}
|
|
return( parts );
|
|
}
|
|
return {};
|
|
} // split
|
|
|
|
|
|
/*!
|
|
* \brief Запаковывает сообщение для передачи через систему pubsub
|
|
* \param hash Таблица значений
|
|
* \return Запакованное сообщение
|
|
*/
|
|
MYXLIB_INLINE QByteArray pack_hash( const QVariantHash& hash )
|
|
{
|
|
QVariantList parts;
|
|
for ( auto iter = hash.cbegin(); iter != hash.cend(); ++iter )
|
|
{
|
|
parts << iter.key() << iter.value();
|
|
}
|
|
return( array( parts ) );
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief Распаковывает кодограмму в таблицу значений
|
|
* \param message Сообщение
|
|
* \return Таблица значений
|
|
*/
|
|
MYXLIB_INLINE QVariantHash unpack_hash( const QByteArray& message )
|
|
{
|
|
auto parts = split( message );
|
|
QVariantHash hash;
|
|
for ( int i = 1; i < parts.size(); i += 2 )
|
|
{
|
|
hash[parts[i - 1]] = parts[i];
|
|
}
|
|
return( hash );
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief Запаковывает сообщение для передачи через систему pubsub
|
|
* \param hash Список значений
|
|
* \return Запакованное сообщение
|
|
*/
|
|
MYXLIB_INLINE QByteArray pack_list( const QVariantList& list )
|
|
{
|
|
return( array( list ) );
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief Распаковывает кодограмму в список значений
|
|
* \param message Сообщение
|
|
* \return Список значений
|
|
*/
|
|
MYXLIB_INLINE QVariantList unpack_list( const QByteArray& message )
|
|
{
|
|
auto data = split( message );
|
|
QVariantList list;
|
|
std::copy( data.begin(), data.end(), std::back_inserter( list ) );
|
|
return( list );
|
|
}
|
|
|
|
} // namespace redis
|
|
|
|
} // namespace myx
|
|
#endif // ifndef MYX_REDIS_CONTAINERS_INL_HPP_
|