myxlib/src/myx/redis/containers-inl.hpp

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_