#ifndef MYX_REDIS_CONTAINERS_INL_HPP_ #define MYX_REDIS_CONTAINERS_INL_HPP_ #pragma once #ifndef MYXLIB_HEADER_ONLY #include #endif #include #include 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_