]> gitweb.hamatoma.de Git - reqt/commitdiff
initial state
authorJ. Hamatoma <hama@siduction.net>
Sun, 24 Nov 2013 08:50:29 +0000 (09:50 +0100)
committerJ. Hamatoma <hama@siduction.net>
Sun, 24 Nov 2013 08:50:29 +0000 (09:50 +0100)
16 files changed:
rplqt/core/rplconfig.cpp [new file with mode: 0644]
rplqt/core/rplconfig.hpp [new file with mode: 0644]
rplqt/core/rplcontainer.cpp [new file with mode: 0644]
rplqt/core/rplcontainer.hpp [new file with mode: 0644]
rplqt/core/rplcore.hpp [new file with mode: 0644]
rplqt/core/rplexception.cpp [new file with mode: 0644]
rplqt/core/rplexception.hpp [new file with mode: 0644]
rplqt/core/rpllogger.cpp [new file with mode: 0644]
rplqt/core/rpllogger.hpp [new file with mode: 0644]
rplqt/core/rplstring.cpp [new file with mode: 0644]
rplqt/core/rplstring.hpp [new file with mode: 0644]
rplqt/core/rpltest.cpp [new file with mode: 0644]
rplqt/core/rpltest.hpp [new file with mode: 0644]
rplqt/util/rpltcpserver.cpp [new file with mode: 0644]
rplqt/util/rpltcpserver.hpp [new file with mode: 0644]
rplqt/util/rplutil.hpp [new file with mode: 0644]

diff --git a/rplqt/core/rplconfig.cpp b/rplqt/core/rplconfig.cpp
new file mode 100644 (file)
index 0000000..9b9091f
--- /dev/null
@@ -0,0 +1,213 @@
+#include "rplcore.hpp"
+
+enum Locations{
+    LOC_WRITE_1 = RPL_FIRST_OF(RPLMODULE_CONFIG),
+    LOC_WRITE_2,
+    LOC_READ_1,
+    LOC_READ_2,
+};
+
+
+/**
+ * Constructor.
+ *
+ * Initializes the logger and reads the configuration file
+ *
+ * @param file          name of the configuration file. May be NULL
+ * @param readOnly      true: the configuration must not be written
+ * @param logger        NULL or a logger
+ */
+RplConfig::RplConfig(const char* file, bool readOnly, RplLogger* logger) :
+    m_file(file),
+    m_lineList(),
+    m_readOnly(readOnly),
+    m_logger(logger),
+    m_ownLogger(logger == NULL)
+{
+    if (logger == NULL){
+        initLogger();
+    }
+    if (file != NULL)
+        read(file);
+}
+
+
+/**
+ * Destructor.
+ *
+ * Frees the resources.
+ */
+RplConfig::~RplConfig(){
+    if (m_ownLogger)
+        delete m_logger;
+    m_logger = NULL;
+}
+
+/**
+ * Inititializes a logger.
+ */
+void RplConfig::initLogger(){
+    m_logger = new RplLogger();
+    RplMemoryAppender* appender = new RplMemoryAppender();
+    appender->setAutoDelete(true);
+    m_logger->addAppender(appender);
+
+    RplStreamAppender* appender2 = new RplStreamAppender(stdout);
+    appender2->setAutoDelete(true);
+    m_logger->addAppender(appender2);
+}
+
+/**
+ * Returns  configuration value as an integer.
+ *
+ * @param key           key of the wanted value
+ * @param defaultValue  if the key does not exist this is the result
+ * @return              defaultValue: key does not exist
+ *                      otherwise: the value assigned to key
+ */
+int RplConfig::asInt(const char* key, int defaultValue) const{
+    int rc = defaultValue;
+    if (contains(key)){
+        rc = atoi((*this)[key]);
+    }
+    return rc;
+}
+
+/**
+ * Returns the configuration value as a boolean.
+ *
+ * @param key           key of the wanted value
+ * @param defaultValue  if the key does not exist this is the result
+ * @return              defaultValue: key does not exist
+ *                      otherwise: the value assigned to key
+ */
+bool RplConfig::asBool(const char* key, bool defaultValue) const{
+    bool rc = defaultValue;
+    if (contains(key)){
+        QByteArray value = (*this)[key].toLower();
+        rc = value == "1" || value == "y" || value == "yes" || value == "t" || value == "true";
+    }
+
+    return rc;
+}
+
+/**
+ * Returns a configuration value.
+ *
+ * @param key           key of the wanted value
+ * @param defaultValue  if the key does not exist this is the result
+ * @return              defaultValue: key does not exist
+ */
+QByteArray RplConfig::asString(const char* key, const char* defaultValue){
+    QByteArray rc = defaultValue;
+    if (contains(key)){
+        rc = (*this)[key];
+    }
+    return rc;
+}
+
+/**
+ * Reads a configuration file.
+ *
+ * @param file  file to read.
+ * @return      true: OK<br>
+ *              false: error occurred
+ */
+bool RplConfig::read(const char* file){
+    bool rc = true;
+    m_lineList.reserve(1024);
+    FILE* fp = fopen(file, "r");
+    if (fp == NULL){
+        m_logger->logv(LOG_ERROR, LOC_READ_1, "cannot read: %s", file);
+        rc = false;
+    } else{
+        char line[64000];
+        char* separator;
+        int lineNo = 0;
+        while(fgets(line, sizeof line, fp) != NULL){
+            lineNo++;
+            m_lineList.append(line);
+            if(isalnum(line[0]) && (separator = strchr(line, '=')) != NULL){
+                QByteArray key(line, separator - line);
+                QByteArray value(separator + 1);
+                key = key.trimmed();
+                value = value.trimmed();
+                if (contains(key))
+                    m_logger->logv(LOG_WARNING, LOC_READ_2, "defined more than once: %s-%d: %s",
+                        file, lineNo, line);
+                else
+                    insert(key, value);
+            }
+        }
+    }
+    return rc;
+}
+
+/**
+ * Writes a configuration file.
+ *
+ * @param file  file to write.
+ * @return      true: OK<br>
+ *              false: error occurred
+ */
+bool RplConfig::write(const char* file){
+    bool rc = false;
+    if (m_readOnly)
+        m_logger->log(LOG_ERROR, LOC_WRITE_1, "cannot write: (readonly");
+    else{
+        m_logger->log(LOG_ERROR, LOC_WRITE_2, "not implemented: write()");
+    }
+    return rc;
+}
+
+// ------------------
+#if ! defined RPL_TEST
+#define RPL_TEST
+#endif
+#ifdef RPL_TEST
+#include "core/rpltest.hpp"
+class ConfigTest : public RplTest
+{
+public:
+    ConfigTest() : RplTest("RplConfig"){}
+
+public:
+    void testBasic(){
+        QByteArray fn = getTempFile("test.data", "config");
+        RplString::write( fn, "#comment\na=1\nb.1==x\n#=\nB=zzz");
+        RplConfig config(fn.constData());
+        checkE(3, config.size());
+        checkE("1", config["a"]);
+        checkE("=x", config["b.1"]);
+        checkE("zzz", config["B"]);
+    }
+    void testAsX(){
+        QByteArray fn = getTempFile("test.data", "config");
+        RplString::write( fn, "i=123\nb=1\nb2=true\nb3=yes\ns=abc");
+        RplConfig config(fn.constData());
+        checkE(5, config.size());
+        checkE(123, config.asInt("i", -1));
+        checkE(-1, config.asInt("I", -1));
+        checkT(config.asBool("b", false));
+        checkT(config.asBool("b2", false));
+        checkT(config.asBool("b3", false));
+        checkT(config.asBool("-", true));
+        checkF(config.asBool("-", false));
+        checkE("abc", config.asString("s", "x"));
+        checkE("x", config.asString("S", "x"));
+    }
+
+    virtual void doIt(){
+        testAsX();
+        testBasic();
+
+    }
+};
+
+#endif
+void testRplConfig(){
+#ifdef RPL_TEST
+    ConfigTest test;
+    test.run();
+#endif
+}
diff --git a/rplqt/core/rplconfig.hpp b/rplqt/core/rplconfig.hpp
new file mode 100644 (file)
index 0000000..beab9dd
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef RPLCONFIG_HPP
+#define RPLCONFIG_HPP
+
+/**
+ * Imports and exports a configuration file into a QHash instance.
+ *
+ * The format of the file:<br>
+ * DEFS or COMMENTS
+ *
+ * DEFS ::= KEY=VALUE
+ *
+ * KEY is a string starting with an alphanumeric character and does not contain a '='
+ *
+ * VALUE is a string
+ */
+class RplConfig : public QHash<QByteArray, QByteArray>
+{
+public:
+    RplConfig(const char* file = NULL, bool readOnly = true, RplLogger* logger = NULL);
+    virtual ~RplConfig();
+
+public:
+    bool read(const char* file);
+    bool write(const char* file);
+    void clear();
+    const QVector<QByteArray>& getLines() const;
+    int asInt(const char* key, int defaultValue) const;
+    bool asBool(const char* key, bool defaultValue) const;
+    QByteArray asString(const char* key, const char* defaultValue);
+private:
+    void initLogger();
+private:
+    const char* m_file;
+    QVector<QByteArray> m_lineList;
+    bool m_readOnly;
+    RplLogger* m_logger;
+    // true: the logger must be destroyed in the destructor
+    bool m_ownLogger;
+};
+
+#endif // RPLCONFIG_HPP
diff --git a/rplqt/core/rplcontainer.cpp b/rplqt/core/rplcontainer.cpp
new file mode 100644 (file)
index 0000000..39e0221
--- /dev/null
@@ -0,0 +1,515 @@
+#include "core/rplcore.hpp"
+
+enum {
+    // 11000
+    LOC_FILL_1 = RPLMODULE_CONTAINER * 1000,
+    LOC_FILL_2,
+    LOC_FILL_3,
+    LOC_NEXT_BAG_1,
+    LOC_NEXT_ITEM_1,
+    LOC_NEXT_ITEM_2 = 11005,
+    LOC_NEXT_INT_1,
+    LOC_NEXT_ITEM_3,
+    LOC_NEXT_BAG_2,
+};
+
+const char* RplContainer::MAGIC_1 = "Rpl&1";
+
+RplContainer::RplContainer(size_t sizeHint) :
+    m_data(""),
+    m_countBags(0),
+    m_typeList(""),
+    m_ixItem(0),
+    m_ixBag(0),
+    m_readPosition(NULL)
+{
+}
+/**
+ * Adds an type to the type list.
+ * @param tag   the type tag
+ */
+void RplContainer::addType(type_tag_t tag){
+    if (m_countBags == 0)
+        startBag();
+    if (m_countBags == 1)
+        m_typeList.append((char) tag);
+}
+
+/**
+ * Starts a new bag.
+ */
+void RplContainer::startBag(){
+    m_countBags++;
+    m_ixBag = 0;
+}
+/**
+ * Adds a character to the current bag.
+ *
+ * @param value    value to insert
+ */
+void RplContainer::addChar(char value){
+    addType(TAG_CHAR);
+    //if (m_typeList.at(m_ixBag) != TAG_INT)
+    //   RplLogger::logAndThrow(LOG_ERROR, __FILE__, __LINE__, 1, "unexpected type: %c instead of c", m_typeList.at(m_ixBag));
+    m_data.append(value);
+}
+/**
+ * Adds an integer to the current bag.
+ *
+ * @param value    value to add
+ */
+void RplContainer::addInt(int value){
+    addType(TAG_INT);
+    char buffer[64];
+    char* ptr = buffer;
+    if (value < 0){
+        *ptr++ = '-';
+        value = - value;
+    }
+    snprintf(ptr, sizeof buffer - 1, "%x ", value);
+    m_data.append(buffer);
+}
+/**
+ * Adds an integer to the current bag.
+ *
+ * @param value    value to add
+ */
+void RplContainer::addInt(qint64 value){
+    addType(TAG_INT);
+    char buffer[128];
+    snprintf(buffer, sizeof buffer, "%llx ", value);
+    m_data.append(buffer);
+}
+
+/**
+ * Adds a string to the current bag.
+ *
+ * @param value    value to add
+ */
+void RplContainer::addString(const char* value){
+    addType(TAG_STRING);
+    // store with trailing '\0'
+    m_data.append(value, strlen(value) + 1);
+}
+/**
+ * Adds binary data to the current bag.
+ * @param value     binary data
+ * @param size      size of the binary data in bytes
+ */
+void RplContainer::addData(byte_t* value, size_t size){
+    if (size <= 255){
+        addType(TAG_DATA255);
+        m_data.append((char) size);
+    } else if (size <= 0xffff){
+        addType(TAG_DATA64K);
+        m_data.append((char) (size / 256));
+        m_data.append((char) (size % 256));
+        m_data.append((const char*) value, size);
+    } else {
+        addType(TAG_DATA4G);
+        m_data.append((char) (size / 256 / 256 / 256));
+        m_data.append((char) (size / 256 / 256 % 256));
+        m_data.append((char) (size / 256 % 256));
+        m_data.append((char) (size % 256));
+        m_data.append((const char*) value, size);
+    }
+    addType(TAG_DATA255);
+
+}
+
+/**
+ * Returns the container byte buffer.
+ *
+ * @return the container as a byte array
+ */
+const QByteArray& RplContainer::getData() {
+    if (m_typeList.length() != 0){
+        char buffer[128];
+        // RPL&1 0a b5[2]cis: !12
+        snprintf(buffer, sizeof buffer, "%x[%d]%s:", (unsigned int) m_data.length(), m_countBags, m_typeList.data());
+        char header[128+8];
+        snprintf(header, sizeof header, "%s%02x%s", MAGIC_1, (unsigned int) strlen(buffer), buffer);
+        m_data.insert(0, header);
+    }
+    return m_data;
+}
+
+/**
+ * Fills the container with an byte array.
+ *
+ * @param data      the container as a byte array
+ */
+void RplContainer::fill(const QByteArray& data){
+    m_data = data;
+    const char* ptr = m_data.data();
+    if (strncmp(ptr, MAGIC_1, strlen(MAGIC_1)) != 0)
+        throw RplInvalidDataException(LOG_ERROR, LOC_FILL_1, "container has no magic", data.data(), data.length());
+    ptr += strlen(MAGIC_1);
+    unsigned int headerSize = 0;
+    if (sscanf(ptr, "%02x", &headerSize) != 1)
+        throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no header size", ptr, 2);
+    ptr += 2;
+
+    unsigned int dataSize = 0;
+    unsigned int countBags = 0;
+    if (sscanf(ptr, "%x[%x]", &dataSize, &countBags) != 2)
+        throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no data_size[bag_count]", ptr, 16);
+    m_countBags = countBags;
+    ptr = strchr(ptr, ']') + 1;
+    const char* end = ptr + strspn(ptr, "cisdDX!");
+    if (end == ptr || *end != ':'){
+        throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no valid typelist", ptr, 16);
+    }
+    m_typeList.clear();
+    m_typeList.append(ptr, end - ptr);
+    m_ixBag = -1;
+    m_readPosition = (byte_t*) end + 1;
+
+
+}
+/**
+ * Returns the number of bags in the container.
+ * @return the number of bags
+ */
+int RplContainer::getCountBags() const{
+    return m_countBags;
+}
+/**
+ * Sets the begin of the new bag.
+ */
+void RplContainer::nextBag(){
+    if (m_ixItem < m_typeList.length() && m_ixItem != -1)
+        throw RplException(LOG_ERROR, LOC_NEXT_BAG_1, NULL,
+            "end of bag not reached: remaining items: %s",
+            m_typeList.data() + m_ixItem);
+    m_ixItem = 0;
+    m_ixBag++;
+    if (m_ixBag >= m_countBags)
+        throw RplException(LOG_ERROR, LOC_NEXT_BAG_2, NULL,
+            "no more bags: %d", m_ixBag);
+}
+/**
+ * Sets the next item.
+ *
+ * @param expected      the expected data type
+ */
+void RplContainer::nextItem(type_tag_t expected){
+    if (m_ixBag < 0){
+        m_ixBag = 0;
+        m_ixItem = 0;
+    }
+    if (m_ixItem >= m_typeList.length())
+        throw RplException(LOG_ERROR, LOC_NEXT_ITEM_1, "no more items in the bag");
+    type_tag_t current = (type_tag_t) m_typeList.at(m_ixItem);
+    // Unify all data types:
+    if (current == TAG_DATA4G || current == TAG_DATA64K)
+        current = TAG_DATA255;
+    if (current != expected)
+        throw RplException(LOG_ERROR, LOC_NEXT_ITEM_2, NULL, "current item is a %c, not a %c",
+            (char) m_typeList.at(m_ixItem), (char) expected);
+    m_ixItem++;
+    if (m_readPosition > (byte_t*) (m_data.data() + m_data.length()))
+        throw RplException(LOG_ERROR, LOC_NEXT_ITEM_3, NULL, "container size too small. Bag: %d of %d Item: %d of %d",
+                          1 + m_ixBag, m_countBags, 1 + m_ixItem, m_typeList.length());
+}
+
+/**
+ * Reads the next character from the current item in the current bag.
+ *
+ * @return  the next char from the container
+ */
+char RplContainer::nextChar(){
+    nextItem(TAG_CHAR);
+    char rc = *m_readPosition++;
+    return rc;
+}
+
+/**
+ * Reads the next integer from the current item in the current bag.
+ *
+ * @return  the next integer from the container
+ */
+int RplContainer::nextInt(){
+    nextItem(TAG_INT);
+    bool isNegativ = *m_readPosition == '-';
+    if (isNegativ)
+        m_readPosition++;
+    unsigned int value = 0;
+    if (sscanf((const char*) m_readPosition, "%x ", &value) != 1)
+        throw RplInvalidDataException(LOG_ERROR, LOC_NEXT_INT_1, "not a hex_number<blank>", m_readPosition, 16);
+    m_readPosition =  (byte_t*) strchr((const char*) m_readPosition, ' ') + 1;
+    if (isNegativ)
+        value = - value;
+    return value;
+}
+/**
+ * Reads the next integer from the current item in the current bag.
+ *
+ * @return  the next integer from the container
+ */
+qint64 RplContainer::nextInt64(){
+    nextItem(TAG_INT);
+    bool isNegativ = *m_readPosition == '-';
+    if (isNegativ)
+        m_readPosition++;
+    qint64 value = 0;
+    if (sscanf((const char*) m_readPosition, "%llx ", &value) != 1)
+        throw RplInvalidDataException(LOG_ERROR, LOC_NEXT_INT_1, "not a hex_number<blank>", m_readPosition, 16);
+    m_readPosition = (byte_t*) strchr((const char*) m_readPosition, ' ') + 1;
+    if (isNegativ)
+        value = - value;
+    return value;
+}
+
+/**
+ * Reads the next string from the current item in the current bag.
+ *
+ * @return  the next '\0' delimited string from the container
+ */
+const char* RplContainer::nextString(){
+    nextItem(TAG_STRING);
+    const char* rc = (const char*) m_readPosition;
+    m_readPosition += strlen(rc) + 1;
+    return rc;
+}
+
+/**
+ * Reads the next string from the current item in the current bag.
+ *
+ * @param data      OUT: the next data item from the container
+ * @param append    true: the item data will be appended to data<br>
+ *                  false: data contains the item data only
+ * @return          the size of the read data
+ */
+size_t RplContainer::nextData(QByteArray& data, bool append){
+    nextItem(TAG_DATA255);
+    type_tag_t tag = (type_tag_t) m_typeList.at(m_ixItem - 1);
+    size_t length = 0;
+    switch(tag){
+    case TAG_DATA4G:
+        for (int ix = 3; ix >= 0; ix--){
+            length = 256 * length + m_readPosition[ix];
+        }
+        m_readPosition += 4;
+        break;
+    case TAG_DATA64K:
+        length = *m_readPosition++ * 256;
+        length += *m_readPosition++;
+        break;
+    case TAG_DATA255:
+        length = *m_readPosition++;
+        break;
+    default:
+        break;
+    }
+    if (! append)
+        data.clear();
+    data.append((const char*) m_readPosition, length);
+    m_readPosition += length;
+    return length;
+}
+
+/**
+ * Return an integer as an QByteArray.
+ *
+ * @param value     value to convert
+ * @param format    format like in sprintf()
+ * @return          the ascii form of the value
+ */
+QByteArray RplContainer::toNumber(int value, const char* format){
+    char buffer[128];
+    snprintf(buffer, sizeof buffer, format, value);
+    return QByteArray(buffer);
+}
+
+/**
+ * Returns a string with a given maximum length.
+ *
+ * @param source        the source
+ * @param maxLength     the maximum length of the result
+ * @param buffer        Out: used if length of the result is shorter
+ * @param appendix      if the result is cut this string will be appended.<br>
+ *                      May be NULL.
+ * @return              source: the source is enough short<br>
+ *                      the prefix of source with the given length
+ */
+const QByteArray& RplContainer::cutString(const QByteArray& source, int maxLength,
+        QByteArray& buffer, const char* appendix){
+    QByteArray& rc = source.length() <= maxLength ? (QByteArray&) source : buffer;
+    if (source.length() > maxLength){
+        buffer = source.left(maxLength);
+        if (appendix != NULL && appendix[0] != '\0')
+            buffer.append(appendix);
+    }
+    return rc;
+}
+
+/**
+ * Builds a hexadecimal dump.
+ *
+ * Format: a sequence of hex digits followed by the ascii interpretation.
+ *
+ * Example: "42 30 61  B0a"
+ *
+ * @param data              data to convert
+ * @param length            length of data
+ * @param bytesPerLine      one line containes so many bytes of data
+ * @return                  the hex dump
+ */
+QByteArray RplContainer::hexDump(byte_t* data, int length, int bytesPerLine){
+    QByteArray rc;
+    rc.reserve(length * 3 + length / bytesPerLine * 2 + 1 + 100);
+    int ixData = 0;
+    int col;
+    char buffer[16];
+    // full filled lines:
+    for (int lineNo = 0; lineNo < length / bytesPerLine * bytesPerLine; lineNo++){
+        for (col = 0; col < bytesPerLine; col++){
+            snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]);
+            rc.append(buffer);
+        }
+        rc.append(' ');
+        for (col = 0; col < bytesPerLine; col++){
+            byte_t cc = data[ixData + col];
+            rc.append(cc > ' ' && cc < 128 ? (char) cc : '.');
+        }
+        ixData += bytesPerLine;
+        rc.append('\n');
+    }
+    // incomplete last line:
+    int restBytes = length - ixData - 1;
+    if (restBytes > 0){
+        for (col = 0; col < restBytes; col++){
+            snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]);
+            rc.append(buffer);
+        }
+        for (col = restBytes; col < bytesPerLine; col++){
+            rc.append("   ");
+        }
+        for (col = 0; col < restBytes; col++){
+            byte_t cc = data[ixData + col];
+            rc.append(cc > ' ' && cc < 128 ? (char) cc : '.');
+        }
+        rc.append('\n');
+    }
+    return rc;
+}
+
+QByteArray RplContainer::dump(const char* title,
+        int maxBags, int maxStringLength, int maxBlobLength,
+        char separatorItems){
+    QByteArray rc;
+    rc.reserve(64000);
+    rc.append("=== ").append(title).append('\n');
+    rc.append("Bags: ").append(toNumber(m_countBags));
+    rc.append(" Types: ").append(m_typeList).append('\n');
+    // save the current state:
+    int safeIxBag = m_ixBag;
+    int safeIxItem = m_ixItem;
+    m_ixBag = -1;
+    m_ixItem = 0;
+    int iValue;
+    QByteArray sValue;
+    if (maxBags > m_countBags)
+        maxBags = m_countBags;
+    for (int ixBag = 0; ixBag < maxBags; ixBag++){
+        rc.append("--- bag ").append(toNumber(ixBag)).append(":\n");
+        nextBag();
+        QByteArray item;
+        int maxLength;
+        for (int ixItem; ixItem < m_typeList.length(); ixItem++){
+            type_tag_t currentType = (type_tag_t) m_typeList.at(ixItem);
+            switch(currentType){
+            case TAG_CHAR:
+                rc.append(" c: ").append(nextChar()).append(separatorItems);
+                break;
+            case TAG_INT:
+                iValue = nextInt();
+                rc.append(" i: ").append(toNumber(iValue)).append(" / ");
+                rc.append(toNumber(iValue, "%x")).append(separatorItems);
+                break;
+            case TAG_STRING:
+                sValue = nextString();
+                if (sValue.length() > maxStringLength)
+                    sValue = sValue.left(maxStringLength);
+                rc.append(" s: ").append(sValue).append(separatorItems);
+                break;
+            case TAG_DATA255:
+            case TAG_DATA64K:
+            case TAG_DATA4G:
+                nextData(item, false);
+                rc.append(' ').append((char) currentType).append(": [");
+                rc.append(toNumber(item.length())).append("] ");
+                maxLength = item.length() < maxBlobLength ? item.length() : maxBlobLength;
+                rc.append(hexDump(item.data(), maxLength, 16)).append(separatorItems);
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    // restore the current state:
+    m_ixBag = safeIxBag;
+    m_ixItem = safeIxItem;
+    return rc;
+}
+
+// ------------------
+#if ! defined RPL_TEST
+#define RPL_TEST
+#endif
+#ifdef RPL_TEST
+#include "core/rpltest.hpp"
+class ContainerTest : public RplTest
+{
+public:
+    ContainerTest() : RplTest("RplContainer"){}
+
+public:
+    void testBasic(){
+        RplContainer container(256);
+        // RPL&1 0a b5[2]cis: !123 Nirwana &lt;0&gt; Y -ab34 A long string with an trailing '0' &lt;0&gt<br>
+        container.startBag();
+        container.addChar('!');
+        container.addInt(123);
+        container.addString("Nirwana");
+        container.startBag();
+        container.addChar('Y');
+        container.addInt(-0xab34);
+        container.addString("A long string with an trailing '0'");
+
+        QByteArray data = container.getData();
+
+        RplContainer container2(256);
+        container2.fill(data);
+        check(2, container2.getCountBags());
+        check('!', container2.nextChar());
+        check(123, container2.nextInt());
+        check("Nirwana", container2.nextString());
+        container2.nextBag();
+        check('Y', container2.nextChar());
+        check(-0xab34, container2.nextInt());
+        check("A long string with an trailing '0'", container2.nextString());
+     }
+    void testHexDump(){
+        QByteArray data("abc123\nxyz");
+        check(QByteArray(""),
+              RplContainer::hexDump((RplContainer::byte_t*) data.data(), data.length(), 4));
+        check(QByteArray(""),
+              RplContainer::hexDump((RplContainer::byte_t*)data.data(), data.length(), 16));
+    }
+
+    virtual void doIt(){
+        testHexDump();
+        testBasic();
+    }
+};
+
+#endif
+void testRplContainer(){
+#ifdef RPL_TEST
+    ContainerTest test;
+    test.run();
+#endif
+}
diff --git a/rplqt/core/rplcontainer.hpp b/rplqt/core/rplcontainer.hpp
new file mode 100644 (file)
index 0000000..e0f5d18
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef RPLCONTAINER_HPP
+#define RPLCONTAINER_HPP
+
+// the sources generated from QT include this file directly:
+#ifndef RPLCORE_HPP
+#include <QByteArray>
+#include <QDataStream>
+#endif
+/**
+ * @brief Implements a portable data container.
+ *
+ * The container contains a list of "bags".
+ * Each bag contains a sequence of items (with a simple data type).
+ * The items are portable: transported to another architecture
+ * the item is restored correct (independent of big/little endian).
+ *
+ * Format:
+ * container ::= magic header_size_hex2 container_size_hex '[' list_count ']' bag_descr list_of_bags<br>
+ * list_of_bag ::= bag1 bag2 ...<br>
+ * bag_descr ::= bag_type1 bag_type2 ... ':'<br>
+ * bag_types := i(nteger) ' ' | s(tring) | b(indata255) | B(indata64k) X(blob4G)<br>
+ * item_list ::= item1 item2...<br>
+ * The binary items (bBX) are byte sequences with a starting size element.
+ * The size element can be a byte (255) or a word (64k) or a double word(4G).
+ * The order of the size element is big endian.
+ *
+ * Example (additional blanks for readibility:
+ *
+ * RPL&1 0a b5[2]cis: !123 Nirwana &lt;0&gt; Y 334455 A long string with an trailing '0' &lt;0&gt<br>
+ * magic header length: 0ah data length: b5h bag count: 2 item types: char int string
+ */
+class RplContainer
+{
+public:
+    typedef enum {
+        TAG_CHAR = 'c',
+        TAG_INT = 'i',
+        TAG_STRING = 's',
+        TAG_DATA255 = 'd',
+        TAG_DATA64K = 'D',
+        TAG_DATA4G = 'X',
+        TAG_CONTAINER = '!'
+    } type_tag_t;
+    static const char* MAGIC_1;
+public:
+    RplContainer(size_t sizeHint);
+public:
+    // Building the container:
+    void addType(type_tag_t tag);
+    void startBag();
+    void addChar(char cc);
+    void addInt(int value);
+    void addInt(qint64 value);
+    void addString(const char* value);
+    void addData(uint8_t* value, size_t size);
+    const QByteArray& getData();
+
+    // Getting data from the container:
+    void fill(const QByteArray& data);
+    int getCountBags() const;
+    const char* getTypeList() const;
+    void nextBag();
+    char nextChar();
+    int nextInt();
+    qint64 nextInt64();
+    const char* nextString();
+    size_t nextData(QByteArray& data, bool append = false);
+
+    QByteArray dump(const char* title,
+        int maxBags, int maxStringLength = 80, int maxBlobLength = 16,
+        char separatorItems = '\n');
+private:
+    void nextItem(type_tag_t expected);
+private:
+    // the complete data of the container
+    QByteArray m_data;
+    // the number of elements in the container
+    int m_countBags;
+    // a string with the data types of a bag
+    QByteArray m_typeList;
+
+    // Getting data from the container:
+
+    // current read position in m_typeList
+    int m_ixItem;
+    // the index of the current current bag:
+    int m_ixBag;
+    // read position in m_data:
+    const uint8_t* m_readPosition;
+};
+
+#endif // RPLCONTAINER_HPP
diff --git a/rplqt/core/rplcore.hpp b/rplqt/core/rplcore.hpp
new file mode 100644 (file)
index 0000000..0642fb4
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef RPLCORE_HPP
+#define RPLCORE_HPP
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <QAbstractSocket>
+#include <QTcpSocket>
+#include <QTcpServer>
+#include <QThread>
+#include <QIODevice>
+#include <QTextStream>
+#include <QHash>
+#include <QVector>
+#include <QDataStream>
+
+typedef unsigned char uint8_t;
+#include "../rplmodules.hpp"
+#include "rpllogger.hpp"
+#include "rplexception.hpp"
+#include "rplcontainer.hpp"
+#include "rplstring.hpp"
+#include "rplconfig.hpp"
+
+#endif // RPLCORE_HPP
diff --git a/rplqt/core/rplexception.cpp b/rplqt/core/rplexception.cpp
new file mode 100644 (file)
index 0000000..8bc25a9
--- /dev/null
@@ -0,0 +1,97 @@
+#include "core/rplcore.hpp"
+
+
+RplException::RplException(const char* message) :
+    m_message(message)
+{
+}
+RplException::RplException(const QByteArray& message) :
+    m_message(message)
+{
+}
+RplException::RplException(RplLoggerLevel level, int location, const char* message, RplLogger* logger) :
+    m_message(message)
+{
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(LOG_ERROR, location, message);
+}
+RplException::RplException(RplLoggerLevel level, int location, const QByteArray& message, RplLogger* logger) :
+    m_message(message)
+{
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(LOG_ERROR, location, message);
+}
+RplException::RplException(RplLoggerLevel level, int location, RplLogger* logger, const char* format, ...) :
+    m_message("")
+{
+    char buffer[64000];
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(buffer, sizeof buffer, format, ap);
+    va_end(ap);
+    m_message = buffer;
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(LOG_ERROR, location, buffer);
+}
+RplException::RplException(RplLoggerLevel level, int location, RplLogger* logger,
+        const QByteArray& format, ...) :
+    m_message("")
+{
+    char buffer[64000];
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(buffer, sizeof buffer, format, ap);
+    va_end(ap);
+    m_message = buffer;
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(LOG_ERROR, location, buffer);
+}
+
+RplRangeException::RplRangeException(RplLoggerLevel level, int location, size_t current,
+        size_t lbound, size_t ubound, const char* message, RplLogger* logger) :
+    RplException("")
+{
+    char buffer[64000];
+    if (message == NULL)
+        message = "value outside limits";
+    snprintf(buffer, sizeof buffer, "%s: %lu [%lu, %lu]",
+             message == NULL ? "" : message,
+            current, lbound, ubound);
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(level, location, buffer);
+}
+
+RplInvalidDataException::RplInvalidDataException(RplLoggerLevel level, int location,
+        const char* message, const void* data,
+        size_t dataSize, RplLogger* logger) :
+    RplException("")
+{
+    char buffer[64000];
+    if (message == NULL)
+        message = "invalid data: ";
+    if (data == NULL)
+        data = "";
+    if (dataSize > 16)
+        dataSize = 16;
+    size_t ix;
+    char* ptr = buffer + strlen(buffer);
+    for (ix = 0; ix < dataSize; ix++){
+        snprintf(ptr, sizeof(buffer) - (ptr - buffer) - 1, "%02x ", ((unsigned char*) data)[ix]);
+        ptr += strlen(ptr);
+    }
+    for (ix = 0; ix < dataSize; ix++){
+        char cc = ((char*) data)[ix];
+        if (cc > ' ' && cc <= '~')
+            *ptr++ = cc;
+        else
+            *ptr++ = '.';
+    }
+    if (logger == NULL)
+        logger = RplLogger::globalLogger();
+    logger->log(level, location, buffer);
+}
diff --git a/rplqt/core/rplexception.hpp b/rplqt/core/rplexception.hpp
new file mode 100644 (file)
index 0000000..f0c3b23
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef RPLEXCEPTION_HPP
+#define RPLEXCEPTION_HPP
+
+// the sources generated from QT include this file directly:
+#ifndef RPLCORE_HPP
+#include <QByteArray>
+#endif
+class RplException
+{
+public:
+    RplException(const char* message);
+    RplException(const QByteArray& message);
+    RplException(RplLoggerLevel level, int location, const char* message,
+        RplLogger* logger = NULL);
+    RplException(RplLoggerLevel level, int location, const QByteArray& message,
+        RplLogger* logger = NULL);
+    RplException(RplLoggerLevel level, int location, RplLogger* logger,
+        const char* message, ...);
+    RplException(RplLoggerLevel level, int location, RplLogger* logger,
+        const QByteArray& message, ...);
+    const QByteArray& getMessage() const {
+        return m_message;
+    }
+protected:
+    QByteArray m_message;
+};
+
+class RplRangeException : public RplException{
+public:
+    RplRangeException(RplLoggerLevel level, int location, size_t current, size_t lbound, size_t ubound,
+        const char* message = NULL, RplLogger* logger = NULL);   
+};
+
+class RplInvalidDataException : public RplException{
+public:
+    RplInvalidDataException(RplLoggerLevel level, int location, const char* message,
+        const void* data = NULL, size_t dataSize = 0, RplLogger* logger = NULL);
+};
+
+#endif // RPLEXCEPTION_HPP
diff --git a/rplqt/core/rpllogger.cpp b/rplqt/core/rpllogger.cpp
new file mode 100644 (file)
index 0000000..bf028a0
--- /dev/null
@@ -0,0 +1,449 @@
+#include "core/rplcore.hpp"
+#include <QDir>
+#include <iostream>
+
+enum {
+    LOC_ADD_APPENDER_1 = RPL_FIRST_OF(RPLMODULE_LOGGER), // 10000
+};
+
+RplLogger* RplLogger::m_globalLogger = NULL;
+
+/**
+ * Returns the global logger.
+ *
+ * If it does not exist it will be created (singleton).
+ *
+ * @return the global logger
+ */
+RplLogger* RplLogger::globalLogger(){
+    if (m_globalLogger == NULL){
+        m_globalLogger = new RplLogger();
+        m_globalLogger->buildStandardAppender("globallogger");
+    }
+    return m_globalLogger;
+}
+/**
+ * Frees the resources of the global logger.
+ */
+void RplLogger::destroyGlobalLogger(){
+    delete m_globalLogger;
+    m_globalLogger = NULL;
+}
+
+/**
+ * Constructor.
+ *
+ * @param name         identifies the logger. Useful for RplLogger::findLogger()
+ */
+RplAppender::RplAppender(const QByteArray& name) :
+    m_name(name),
+    m_level(LOG_INFO)
+{
+
+}
+/**
+ * Destructor.
+ */
+RplAppender::~RplAppender(){
+}
+
+/**
+ * Returns the name.
+ *
+ * @return             the name of the instance
+ */
+const char* RplAppender::getName() const{
+       return m_name.data();
+}
+
+/**
+ * Sets the level.
+ *
+ * @param level
+ */
+void RplAppender::setLevel(RplLoggerLevel level){
+    m_level = level;
+}
+/**
+ * Returns the level.
+ *
+ * @return             the level
+ */
+RplLoggerLevel RplAppender::getLevel() const{
+    return m_level;
+}
+/**
+ * Checks whether the current location should be logged.
+ *
+ * @param level                the level of the location.
+ * @return                     true: the location level is greater or equals to the appender's level
+ */
+bool RplAppender::isActive(RplLoggerLevel level){
+    return level <= m_level;
+}
+
+/**
+ * Sets or clears the automatic deletions.
+ *
+ * @param onNotOff             the state of the auto deletion
+ */
+void RplAppender::setAutoDelete(bool onNotOff){
+    m_autoDelete = onNotOff;
+}
+
+/**
+ * Returns the state of the auto deletion.
+ *
+ * @return     true: the logger destroys the instance
+ */
+bool RplAppender::isAutoDelete() const{
+    return m_autoDelete;
+}
+
+/**
+ * Constructor.
+ */
+RplLogger::RplLogger() :
+    m_countAppenders(0)
+{
+    memset(m_appenders, 0, sizeof m_appenders);
+}
+
+/**
+ * Destructor.
+ */
+RplLogger::~RplLogger(){
+    for (size_t ix = 0; ix < m_countAppenders; ix++){
+        RplAppender* appender = m_appenders[ix];
+        if (appender->isAutoDelete()){
+            delete appender;
+        }
+        m_appenders[ix] = NULL;
+    }
+}
+/**
+ * Returns the first char of a logging line displaying the logging level.
+ *
+ * @param level                the level to "convert"
+ * @return                     the assigned prefix char
+ */
+char RplLogger::getPrefixOfLevel(RplLoggerLevel level) const {
+    char rc = ' ';
+    switch(level){
+    case LOG_ERROR:
+        rc = '!';
+        break;
+    case LOG_WARNING:
+        rc = '+';
+        break;
+    case LOG_INFO:
+        rc = ' ';
+        break;
+    case LOG_DEBUG:
+        rc = '=';
+        break;
+    default:
+        rc = '?';
+        break;
+    }
+    return rc;
+}
+/**
+ * Returns the standard prefix of a logging line.
+ *
+ * If it does not exist it will be created.
+ *
+ * @param level                the level of the location
+ * @param file         the source file of the location. @see __FILE__
+ * @param lineNo       the lineNo of the location. @see __LINE__
+ * @param location     an unique identifier of the location
+ * @return                     the standard logging line prefix
+ */
+const QByteArray& RplLogger::getStdPrefix(RplLoggerLevel level, int location){
+    if (m_stdPrefix.isEmpty())
+        m_stdPrefix = buildStdPrefix(level, location);
+    return m_stdPrefix;
+}
+
+/**
+ * Logs (or not) the calling location.
+ *
+ * @param level                the level of the location
+ * @param file         the source file of the location. @see __FILE__
+ * @param lineNo       the lineNo of the location. @see __LINE__
+ * @param location     an unique identifier of the location
+ * @param message      the logging message
+ * @return                     true: for chaining
+ */
+bool RplLogger::log(RplLoggerLevel level, int location, const char* message){
+    m_stdPrefix = "";
+    for (size_t ix = 0; ix < m_countAppenders; ix++){
+        RplAppender* appender = m_appenders[ix];
+        if (appender->isActive(level))
+            appender->log(level, location, message, this);
+    }
+    return true;
+}
+
+/**
+ * Logs (or not) the calling location.
+ *
+ * @param level                the level of the location
+ * @param file         the source file of the location. @see __FILE__
+ * @param lineNo       the lineNo of the location. @see __LINE__
+ * @param location     an unique identifier of the location
+ * @param format       the logging message with placeholders (like printf).
+ * @param ...          the values of the placeholders (varargs)
+ * @return                     true: for chaining
+ */
+bool RplLogger::logv(RplLoggerLevel level, int location, const char* format, ...){
+    char buffer[64000];
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(buffer, sizeof buffer, format, ap);
+    va_end(ap);
+    return log(level, location, buffer);
+}
+
+/**
+ * Logs (or not) the calling location.
+ *
+ * @param level                the level of the location
+ * @param file         the source file of the location. @see __FILE__
+ * @param lineNo       the lineNo of the location. @see __LINE__
+ * @param location     an unique identifier of the location
+ * @param format       the logging message with placeholders (like printf).
+ * @param varlist      variable arguments
+ * @return                     true: for chaining
+ */
+bool RplLogger::log(RplLoggerLevel level, int location, const char* format, va_list& varlist){
+    char buffer[64000];
+    vsnprintf(buffer, sizeof buffer, format, varlist);
+    return log(level, location, buffer);
+}
+
+/**
+ * Builds the standard prefix of a logging line.
+ *
+ * @param level                the level of the location
+ * @param file         the source file of the location. @see __FILE__
+ * @param lineNo       the lineNo of the location. @see __LINE__
+ * @param location     an unique identifier of the location
+ */
+QByteArray RplLogger::buildStdPrefix(RplLoggerLevel level, int location){
+    time_t now = time(NULL);
+    struct tm* now2 = localtime(&now);
+    char buffer[64];
+    snprintf(buffer, sizeof buffer, "%c%d.%02d.%02d %02d:%02d:%02d (%d): ",
+            getPrefixOfLevel(level),
+            now2->tm_year + 1900,
+            now2->tm_mon + 1,
+            now2->tm_mday,
+            now2->tm_hour,
+            now2->tm_min,
+            now2->tm_sec,
+            location);
+    return QByteArray(buffer);
+}
+
+/**
+ * Adds an appender.
+ *
+ * @param appender             appender to add
+ */
+void RplLogger::addAppender(RplAppender* appender){
+    if (m_countAppenders < sizeof m_appenders / sizeof m_appenders[0]){
+        m_appenders[m_countAppenders++] = appender;
+    } else {
+        log(LOG_ERROR, LOC_ADD_APPENDER_1, "too many appenders");
+    }
+}
+
+RplAppender* RplLogger::findAppender(const char* name) const{
+       RplAppender* rc = NULL;
+       for (size_t ix = 0; ix < m_countAppenders; ix++){
+               RplAppender* current = m_appenders[ix];
+               if (strcmp(name, current->getName()) == 0){
+                       rc = current;
+                       break;
+               }
+       }
+       return rc;
+}
+
+/**
+ * Builds the standard appender for the instance: a console logger and a file logger.
+ *
+ * @param prefix               the prefix of the log file name, e.g. /var/log/server
+ * @param maxSize              the maximum of the file size
+ * @param maxCount             the maximal count of files. If neccessary the oldest file will be deleted
+ */
+void RplLogger::buildStandardAppender(const QByteArray& prefix, int maxSize, int maxCount){
+    RplStreamAppender* streamAppender = new RplStreamAppender(stderr);
+    streamAppender->setAutoDelete(true);
+    addAppender((RplAppender*) streamAppender);
+    RplFileAppender* fileAppender = new RplFileAppender(prefix, maxSize, maxCount);
+    fileAppender->setAutoDelete(true);
+    addAppender((RplAppender*) fileAppender);
+
+}
+
+
+/**
+ * Constructor.
+ */
+RplStreamAppender::RplStreamAppender(FILE* file, const char* appenderName) :
+    RplAppender(QByteArray(appenderName)),
+    m_fp(file)
+{
+}
+
+/**
+ * Destructor.
+ */
+RplStreamAppender::~RplStreamAppender(){
+    fflush(m_fp);
+}
+
+
+/**
+ * Logs (or not) the current location.
+ *
+ * @param level                the level of the location
+ * @param location     an unique identifier of the location
+ * @param message      the logging message
+ * @param logger    the calling logger
+ */
+void RplStreamAppender::log(RplLoggerLevel level, int location, const char* message, RplLogger* logger){
+    const QByteArray& prefix = logger->getStdPrefix(level, location);
+    fputs(prefix, m_fp);
+    fputs(message, m_fp);
+    fputc('\n', m_fp);
+    fflush(m_fp);
+}
+#pragma GCC diagnostic warning "-Wunused-parameter"
+
+
+/**
+ * Constructor.
+ *
+ * @param prefix               the prefix of the log file name, e.g. /var/log/server
+ * @param maxSize              the maximum of the file size
+ * @param maxCount             the maximal count of files. If neccessary the oldest file will be deleted
+ * @param appenderName the name of the appender. @see RplLogger::findAppender()
+ */
+RplFileAppender::RplFileAppender(const QByteArray& prefix, int maxSize, int maxCount, const char* appenderName) :
+    RplAppender(QByteArray(appenderName)),
+    m_prefix(prefix),
+    m_maxSize(maxSize),
+    m_maxCount(maxCount),
+    m_currentSize(0),
+    m_currentNo(0),
+    m_fp(NULL)
+{
+    open();
+}
+
+/**
+ * Destructor.
+ */
+RplFileAppender::~RplFileAppender(){
+    if (m_fp != NULL){
+        fclose(m_fp);
+        m_fp = NULL;
+    }
+}
+
+/**
+ * Opens the next log file.
+ */
+void RplFileAppender::open(){
+    if (m_fp != NULL)
+        fclose(m_fp);
+    char fullName[512];
+    snprintf(fullName, sizeof fullName, "%s.%03d.log", m_prefix.data(), ++m_currentNo);
+    m_fp = fopen(fullName, "a");
+    if (m_fp == NULL)
+        fprintf(stderr, "cannot open: %s\n", fullName);
+    else{
+        //@ToDo
+        m_currentSize = 0;
+    }
+}
+
+/**
+ * Logs (or not) the current location.
+ *
+ * @param level                the level of the location
+ * @param location     an unique identifier of the location
+ * @param message      the logging message
+ * @param logger    the calling logger
+ */
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+void RplFileAppender::log(RplLoggerLevel level, int location, const char* message,
+         RplLogger* logger)
+{
+    if (m_fp != NULL){
+        const QByteArray& prefix = logger->getStdPrefix(level, location);
+        fputs(prefix, m_fp);
+        fputs(message, m_fp);
+        fputc('\n', m_fp);
+        fflush(m_fp);
+    }
+}
+#pragma GCC diagnostic warning "-Wunused-parameter"
+
+/**
+ * Constructor.
+ *
+ * @param maxLines      the maximum of lines.
+ *                      If the buffer is full the oldest lines will be deleted
+ * @param appenderName  NULL or the name of the appender
+ */
+RplMemoryAppender::RplMemoryAppender(int maxLines, const char* appenderName) :
+    RplAppender(appenderName),
+    m_lines(),
+    m_maxLines(maxLines)
+{
+    m_lines.reserve(maxLines);
+}
+
+/**
+ * Destructor.
+ */
+RplMemoryAppender::~RplMemoryAppender(){
+}
+
+/**
+ * Logs (or not) the current location.
+ *
+ * @param level                the level of the location
+ * @param location     an unique identifier of the location
+ * @param message      the logging message
+ * @param logger    the calling logger
+ */
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+void RplMemoryAppender::log(RplLoggerLevel level, int location, const char* message,
+         RplLogger* logger)
+{
+    if (m_lines.size() >= m_maxLines)
+        m_lines.removeFirst();
+    m_lines.append(message);
+}
+#pragma GCC diagnostic warning "-Wunused-parameter"
+
+/**
+ * Returns the list of lines.
+ *
+ * @return the line list
+ */
+const QVector<QByteArray>& RplMemoryAppender::getLines() const{
+    return m_lines;
+}
+
+/**
+ * Deletes all log lines.
+ */
+void RplMemoryAppender::clear(){
+    m_lines.clear();
+}
diff --git a/rplqt/core/rpllogger.hpp b/rplqt/core/rpllogger.hpp
new file mode 100644 (file)
index 0000000..80c0d1f
--- /dev/null
@@ -0,0 +1,149 @@
+#ifndef RPLLOGGER_HPP
+#define RPLLOGGER_HPP
+
+class RplLogger;
+
+/**
+ * Logging level: for controlling of the logging.
+ * Each logging location defines one of the following level.
+ * If the level of an appender is lower or equals to this level
+ * the logging is done.
+ */
+enum RplLoggerLevel {
+    LOG_ERROR = 10,
+    LOG_WARNING = 15,
+    LOG_INFO = 20,
+    LOG_DEBUG = 25
+};
+/**
+ * Base class for an object which puts the logging info to a medium like a file.
+ */
+class RplAppender
+{
+public:
+    RplAppender(const QByteArray& name);
+    virtual ~RplAppender();
+    virtual void log(RplLoggerLevel level, int location, const char* message,
+        RplLogger* logger) = 0;
+    bool isActive(RplLoggerLevel level);
+    void setLevel(RplLoggerLevel level);
+    void setAutoDelete(bool onNotOff);
+    bool isAutoDelete() const;
+    RplLoggerLevel getLevel() const;
+    const char* getName() const;
+
+private:
+    // Name of the appender. Used to find the appender in a list of appenders
+    QByteArray m_name;
+    // only locations with a lower or equal level will be logged
+    RplLoggerLevel m_level;
+    // true: the logger destroys the instance. false: the deletion must be done outside of the logger
+    bool m_autoDelete;
+};
+
+/**
+ * Implements a logging facility.
+ * The logging is controlled by a logging level (@see RplLoggerLevel).
+ * The logger must be supported by at least one appender (@see RplAppender)
+ */
+class RplLogger
+{
+public:
+    static RplLogger* globalLogger();
+    static void destroyGlobalLogger();
+private:
+    // the standard logger, can be called (with globalLogger()) from each location
+    static RplLogger* m_globalLogger;
+public:
+    RplLogger();
+    virtual ~RplLogger();
+public:
+    bool log(RplLoggerLevel level, int location, const char* message);
+    bool log(RplLoggerLevel level, int location, const QByteArray& message){
+        return log(level, location, message.data());
+    }
+    bool log(RplLoggerLevel level, int location, const QString& message){
+        return log(level, location, message.toUtf8().data());
+    }
+
+    bool logv(RplLoggerLevel level, int location, const char* format, ...);
+    bool logv(RplLoggerLevel level, int location, const QByteArray& format, ...);
+    bool log(RplLoggerLevel level, int location, const char* format, va_list& varlist);
+    void addAppender(RplAppender* appender);
+    RplAppender* findAppender(const char* name) const;
+    void buildStandardAppender(const QByteArray& prefix, int maxSize = 10*1024*1024, int maxCount = 5);
+    QByteArray buildStdPrefix(RplLoggerLevel level, int location);
+    const QByteArray& getStdPrefix(RplLoggerLevel level, int location);
+    char getPrefixOfLevel(RplLoggerLevel level) const;
+private:
+    // the assigned appenders:
+    RplAppender* m_appenders[16];
+    // the number of appenders in m_appenders:
+    size_t m_countAppenders;
+    // "" or the cache of the prefix of the current logging line: This can be reused by any appender.
+    QByteArray m_stdPrefix;
+};
+
+/**
+ * Implements an appender which puts the messages to a standard stream: stdout or stderr
+ */
+class RplStreamAppender : public RplAppender
+{
+public:
+    RplStreamAppender(FILE* stream, const char* appenderName = "FileAppender");
+    virtual ~RplStreamAppender();
+public:
+    virtual void log(RplLoggerLevel level, int location, const char* message,
+             RplLogger* logger);
+private:
+    // stdout or stderr:
+    FILE* m_fp;
+};
+
+/**
+ * Implements an appender which puts the messages to a file
+ */
+class RplFileAppender : public RplAppender
+{
+public:
+    RplFileAppender(const QByteArray& name, int maxSize, int maxCount, const char* appenderName = "FileAppender");
+    virtual ~RplFileAppender();
+public:
+    void open();
+    virtual void log(RplLoggerLevel level, int location, const char* message,
+             RplLogger* logger);
+
+private:
+    // prefix of the log file name. Will be appended by ".<no>.log"
+    QByteArray m_prefix;
+    // maximal size of a logging file:
+    int m_maxSize;
+    // maximal count of logging files. If neccessary the oldest file will be deleted.
+    int m_maxCount;
+    // the size of the current log file:
+    int m_currentSize;
+    // the number of the current log file:
+    int m_currentNo;
+    // the current log file:
+    FILE* m_fp;
+};
+
+/**
+ * Stores the log messages in a list.
+ */
+class RplMemoryAppender : public RplAppender{
+public:
+    RplMemoryAppender(int maxLines = 1024, const char* appenderName = "MemoryAppender");
+    ~RplMemoryAppender();
+public:
+    virtual void log(RplLoggerLevel level, int location, const char* message,
+             RplLogger* logger);
+    const QVector<QByteArray>& getLines() const;
+    void clear();
+private:
+    QVector<QByteArray> m_lines;
+    // maximum count of m_lines. If larger the oldest lines will be deleted.
+    int m_maxLines;
+};
+
+#endif // RPLLOGGER_HPP
diff --git a/rplqt/core/rplstring.cpp b/rplqt/core/rplstring.cpp
new file mode 100644 (file)
index 0000000..17e4486
--- /dev/null
@@ -0,0 +1,267 @@
+#include "rplcore.hpp"
+
+/**
+ * Counts the occurrences of a string in a string.
+ *
+ * @param source    in this string will be searched
+ * @param item      this item will be searched
+ * @return  the count of occurrences
+ */
+int RplString::count(const char* source, const char* item){
+    const char* end = source;
+    int rc = 0;
+    int lengthItem = strlen(item);
+    while (true){
+        const char* start = end;
+        end = strstr(start, item);
+        if (end == NULL)
+            break;
+        else{
+            rc++;
+            end += lengthItem;
+        }
+    }
+    return rc;
+}
+
+/**
+ * Returns a string with a given maximum length.
+ *
+ * @param source        the source
+ * @param maxLength     the maximum length of the result
+ * @param buffer        Out: used if length of the result is shorter
+ * @param appendix      if the result is cut this string will be appended.<br>
+ *                      May be NULL.
+ * @return              source: the source is enough short<br>
+ *                      the prefix of source with the given length
+ */
+const QByteArray& RplString::cutString(const QByteArray& source, int maxLength,
+        QByteArray& buffer, const char* appendix){
+    QByteArray& rc = source.length() <= maxLength ? (QByteArray&) source : buffer;
+    if (source.length() > maxLength){
+        buffer = source.left(maxLength);
+        if (appendix != NULL && appendix[0] != '\0')
+            buffer.append(appendix);
+    }
+    return rc;
+}
+
+/**
+ * Builds a hexadecimal dump.
+ *
+ * Format: a sequence of hex digits followed by the ascii interpretation.
+ *
+ * Example: "42 30 61  B0a"
+ *
+ * @param data              data to convert
+ * @param length            length of data
+ * @param bytesPerLine      one line containes so many bytes of data
+ * @return                  the hex dump
+ */
+QByteArray RplString::hexDump(uint8_t* data, int length, int bytesPerLine){
+    QByteArray rc;
+    int fullLines = length / bytesPerLine;
+    int expectedLength = (bytesPerLine * 4 + 2) * (fullLines + 1);
+    rc.reserve(expectedLength + 100);
+    int ixData = 0;
+    int col;
+    char buffer[16];
+    for (int lineNo = 0; lineNo < fullLines; lineNo++){
+        for (col = 0; col < bytesPerLine; col++){
+            snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]);
+            rc.append(buffer);
+        }
+        rc.append(' ');
+        for (col = 0; col < bytesPerLine; col++){
+            uint8_t cc = data[ixData + col];
+            rc.append(cc > ' ' && cc < 128 ? (char) cc : '.');
+        }
+        ixData += bytesPerLine;
+        rc.append('\n');
+    }
+    // incomplete last line:
+    int restBytes = length - ixData;
+    if (restBytes > 0){
+        for (col = 0; col < restBytes; col++){
+            snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]);
+            rc.append(buffer);
+        }
+        for (col = restBytes; col < bytesPerLine; col++){
+            rc.append("   ");
+        }
+        rc.append(' ');
+        for (col = 0; col < restBytes; col++){
+            uint8_t cc = data[ixData + col];
+            rc.append(cc > ' ' && cc < 128 ? (char) cc : '.');
+        }
+        rc.append('\n');
+    }
+    return rc;
+}
+
+
+/**
+ * Reads a file into a string.
+ *
+ * @param file              file to read
+ * @param removeLastNewline true: if the last character is a newline
+ *                          the result will not contain this
+ * @return                  the file's content
+ */
+QByteArray RplString::read(const char* file, bool removeLastNewline){
+    QByteArray rc;
+    struct stat info;
+    size_t size;
+    if (stat(file, &info) == 0 && (size = info.st_size) > 0){
+        FILE* fp = fopen(file, "r");
+        if (fp != NULL){
+            rc.resize(info.st_size);
+            fread(rc.data(), 1, size, fp);
+            fclose(fp);
+            if (removeLastNewline && rc.at(size - 1) == '\n'){
+                rc.resize(size - 1);
+            }
+        }
+    }
+    return rc;
+}
+
+/**
+ * Converts a string into an array of strings.
+ *
+ * @param source        string to convert
+ * @param separator     the separator between the items to split
+ * @return              an array with the splitted source
+ */
+QVector<QByteArray> RplString::toArray(const char* source, const char* separator){
+    const char* end = source;
+    QVector<QByteArray> rc;
+    rc.reserve(count(source, separator) + 1);
+    int lengthItem = strlen(separator);
+    while (*end != '\0'){
+        const char* start = end;
+        end = strstr(start, separator);
+        if (end == NULL){
+            end = start + strlen(start);
+        }
+        rc.append(QByteArray(start, end - start));
+        if (end[0] != '\0')
+            end += lengthItem;
+    }
+    return rc;
+}
+
+
+/**
+ * Return an integer as an QByteArray.
+ *
+ * @param value     value to convert
+ * @param format    format like in sprintf()
+ * @return          the ascii form of the value
+ */
+QByteArray RplString::toNumber(int value, const char* format){
+    char buffer[128];
+    snprintf(buffer, sizeof buffer, format, value);
+    return QByteArray(buffer);
+}
+
+/**
+ * Writes a string to a file.
+ *
+ * @param file      the file's name
+ * @param content   NULL or the file's content
+ * @param mode      the file open mode: "w" for write, "a" for append
+ * @return          true: successful<br>
+ *                  false: error occurred
+ */
+bool RplString::write(const char* file, const char* content, const char* mode){
+    FILE* fp = fopen(file, mode);
+    if (fp != NULL){
+        fputs(content, fp);
+        fclose(fp);
+    }
+    return fp != NULL;
+}
+
+// ------------------
+#if ! defined RPL_TEST
+#define RPL_TEST
+#endif
+#ifdef RPL_TEST
+#include "core/rpltest.hpp"
+class StringTest : public RplTest
+{
+public:
+    StringTest() : RplTest("RplString"){}
+
+public:
+    void testCount(){
+        checkE(0, RplString::count("abc", " "));
+        checkE(1, RplString::count("abc", "b"));
+        checkE(2, RplString::count("axx", "x"));
+
+        checkE(0, RplString::count("abbc", "bbb"));
+        checkE(1, RplString::count("\n\n", "\n\n"));
+        checkE(2, RplString::count("  a  ", "  "));
+    }
+
+    void testCutString(){
+        QByteArray source("123");
+        QByteArray buffer;
+        checkE(QByteArray("123"), RplString::cutString(source, 4, buffer));
+        checkE(QByteArray("123"), RplString::cutString(source, 3, buffer));
+        checkE(QByteArray("12..."), RplString::cutString(source, 2, buffer));
+        checkE(QByteArray("12"), RplString::cutString(source, 2, buffer, ""));
+    }
+
+    void testHexDump(){
+        QByteArray data("abc123\nxyz");
+        checkE(QByteArray("61 62 63 31  abc1\n"
+                         "32 33 0a 78  23.x\n"
+                         "79 7a        yz\n"),
+              RplString::hexDump((uint8_t*) data.constData(), data.length(), 4));
+        checkE(QByteArray("61 62 63 31 32 33 0a 78 79 7a  abc123.xyz"),
+              RplString::hexDump((uint8_t*) data.constData(), data.length(), 10));
+        checkE(QByteArray("61 62 63 31 32 33 0a 78 79 7a        abc123.xyz"),
+              RplString::hexDump((uint8_t*) data.constData(), data.length(), 12));
+    }
+
+    void testReadWrite(){
+        QByteArray fn = getTempFile("test.dat");
+        const char* content = "Hello world\nLine2\n";
+        checkT(RplString::write(fn, content));
+        checkE(content, RplString::read(fn, false));
+        checkE(content, RplString::read(fn, true) + "\n");
+     }
+
+    void testToArray(){
+        QVector<QByteArray> array = RplString::toArray("1 abc 3", " ");
+        checkE(3, array.size());
+        checkE("1", array.at(0));
+        checkE("abc", array.at(1));
+        checkE("3", array.at(2));
+    }
+
+    void testToNumber(){
+        checkE("3", RplString::toNumber(3));
+        checkE("-33", RplString::toNumber(-33));
+        checkE("003", RplString::toNumber(3, "%03d"));
+    }
+
+    virtual void doIt(){
+        testCount();
+        testCutString();
+        testToNumber();
+        testToArray();
+        testHexDump();
+        testReadWrite();
+    }
+};
+
+#endif
+void testRplString(){
+#ifdef RPL_TEST
+    StringTest test;
+    test.run();
+#endif
+}
diff --git a/rplqt/core/rplstring.hpp b/rplqt/core/rplstring.hpp
new file mode 100644 (file)
index 0000000..b422e2f
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef RPLSTRING_HPP
+#define RPLSTRING_HPP
+
+/**
+ * Implements some services around strings.
+ *
+ * This is a class with static members only.
+ */
+class RplString
+{
+public:
+    static int count(const char* source, const char* item);
+    static const QByteArray& cutString(const QByteArray& source, int maxLength,
+        QByteArray& buffer, const char* appendix = "...");
+    static QByteArray hexDump(uint8_t* data, int length, int bytesPerLine = 16);
+    static QByteArray hexDump(const void* data, int length, int bytesPerLine = 16){
+        return hexDump((uint8_t*) data, length, bytesPerLine);
+    }
+    static QByteArray read(const char* file, bool removeLastNewline = true);
+    static bool write(const char* file, const char* content = NULL,
+        const char* mode = "w");
+    static QVector<QByteArray> toArray(const char* source, const char* separator);
+    static QByteArray toNumber(int value, const char* format = "%d");
+};
+
+#endif // RPLSTRING_HPP
diff --git a/rplqt/core/rpltest.cpp b/rplqt/core/rpltest.cpp
new file mode 100644 (file)
index 0000000..555d5aa
--- /dev/null
@@ -0,0 +1,313 @@
+#include "core/rplcore.hpp"
+#include "core/rpltest.hpp"
+/**
+ * Constructor.
+ *
+ * @param name
+ */
+RplTest::RplTest(const char* name) :
+    m_errors(0),
+    m_name(name),
+    m_logger()
+{
+    m_logger.buildStandardAppender(getTempDir("rpltest"));
+    log(QByteArray("Start of ") + m_name);
+}
+void RplTest::run(){
+    try {
+        doIt();
+    } catch (RplException e){
+        error("unexpected RplException: %s", e.getMessage().data());
+    }
+
+    if (m_errors > 0){
+        error("Unit %s has %d error(s)", m_name.data(), m_errors);
+        // error() increments, we decrement:
+        m_errors--;
+    }
+}
+
+/**
+ * Destructor.
+ */
+RplTest::~RplTest(){
+}
+
+/**
+ * Tests the equality of two values.
+ *
+ * Differences will be logged.
+ *
+ * @param expected      the expected value
+ * @param current       the current value
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: equal
+ */
+bool RplTest::assertEquals(int expected, int current, const char* file, int lineNo){
+    if (expected != current)
+        error("%s-%d: error: %d != %d / %x != %x)", file, lineNo, expected, current, (unsigned int) expected, (unsigned int) current);
+    return expected == current;
+}
+
+/**
+ * Tests the equality of two values.
+ *
+ * Differences will be logged.
+ *
+ * @param expected      the expected value
+ * @param current       the current value
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: equal
+ */
+bool RplTest::assertEquals(const char* expected, const char* current,
+        const char* file, int lineNo){
+    bool equal = strcmp(expected, current) == 0;
+    if (! equal){
+        if (strchr(expected, '\n') != NULL || strchr(current, '\n')){
+            QVector<QByteArray> exp = RplString::toArray(expected, "\n");
+            QVector<QByteArray> cur = RplString::toArray(current, "\n");
+            equal = assertEquals(exp, cur, file, lineNo);
+        } else {
+            int ix = 0;
+            while(expected[ix] == current[ix] && expected[ix] != '\0')
+                ix++;
+            char pointer[12+1];
+            char *ptr = pointer;
+            int maxIx = ix > 10 ? 10 : ix;
+            for (int ii = 0; ii < maxIx - 1; ii++)
+                *ptr++ = '-';
+            *ptr++ = '^';
+            *ptr = '\0';
+            if (ix < 10)
+                error("%s-%d: error: diff at index %d\n%s\n%s\n%s",
+                      file, lineNo, ix, expected, current, pointer);
+            else
+                error("%s-%d: error: diff at index %d\n%s\n...%s\n...%s\n%s",
+                      file, lineNo, ix, current,
+                      expected + ix - 10 + 3,
+                      current + ix - 10 + 3,
+                      pointer);
+        }
+    }
+    return equal;
+}
+
+/**
+ * Tests the equality of two values.
+ *
+ * Differences will be logged.
+ *
+ * @param expected      the expected value
+ * @param current       the current value
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: equal
+ */
+bool RplTest::assertEquals(const QVector<QByteArray>& expected,
+        const QVector<QByteArray>& current, const char* file, int lineNo){
+    int nMax = expected.size();
+    bool rc = true;
+    if (current.size() < nMax)
+        nMax = current.size();
+    for (int ix = 0; ix < nMax; ix++){
+        if (expected.at(ix) != current.at(ix)){
+            error("%s-%d: difference in line %d", file, lineNo, ix+1);
+            m_errors--;
+            assertEquals(expected.at(ix).constData(), current.at(ix).constData(),
+                file, lineNo);
+            rc = false;
+            break;
+        }
+    }
+    if (rc){
+        if (expected.size() > nMax)
+            error("%s-%d: less lines than expected (%d):\n%s",
+                  file, lineNo, nMax, expected.at(nMax).constData());
+        else if (expected.size() < nMax)
+            error("%s-%d: more lines than expected (%d):\n%s",
+                  file, lineNo, nMax, current.at(nMax).constData());
+    }
+    return rc;
+}
+/**
+ * Tests the equality of two values.
+ *
+ * Differences will be logged.
+ *
+ * @param expected      the expected value
+ * @param current       the current value
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: equal
+ */
+bool RplTest::assertEquals(const QByteArray& expected, const QByteArray& current, const char* file, int lineNo){
+    return assertEquals(expected.data(), current.data(), file, lineNo);
+}
+
+/**
+ * Tests the equality of two values.
+ *
+ * Differences will be logged.
+ *
+ * @param expected      the expected value
+ * @param current       the current value
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: equal
+ */
+bool RplTest::assertEquals(const char* expected, const QByteArray& current,
+        const char* file, int lineNo){
+    return assertEquals(expected, current.constData(), file, lineNo);
+}
+
+/**
+ * Tests whether a value is true.
+ *
+ * A value of false will be logged.
+ *
+ * @param condition     value to test
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              <code>condition</code>
+ */
+bool RplTest::assertTrue(bool condition, const char* file, int lineNo){
+    if (! condition)
+        error("%s-%d: not TRUE", file, lineNo);
+    return condition;
+}
+
+/**
+ * Tests whether a value is false.
+ *
+ * A value of true will be logged.
+ *
+ * @param condition     value to test
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              <code>! condition</code>
+ */
+bool RplTest::assertFalse(bool condition, const char* file, int lineNo){
+    if (condition)
+        error("%s-%d: not FALSE", file, lineNo);
+    return ! condition;
+}
+
+/**
+ * Tests whether a value is NULL.
+ *
+ * A value of not NULL will be logged.
+ *
+ * @param ptr           value to test
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: ptr is NULL
+ */
+bool RplTest::assertNull(const void* ptr, const char* file, int lineNo){
+    if (ptr != NULL)
+        error("%s-%d: not NULL", file, lineNo);
+    return ptr == NULL;
+}
+
+/**
+ * Tests whether a value is not NULL.
+ *
+ * A value of NULL will be logged.
+ *
+ * @param ptr           value to test
+ * @param file          the file containing the test
+ * @param lineNo        the line number containing the test
+ * @return              true: ptr is not NULL
+ */
+bool RplTest::assertNotNull(const void* ptr, const char* file, int lineNo){
+    if (ptr == NULL)
+        error("%s-%d: is NULL", file, lineNo);
+    return ptr != NULL;
+}
+
+/**
+ * Writes an info.
+ *
+ * @param message   message to show
+ * @return          true (for chaining)
+ */
+bool RplTest::log(const char* message){
+    m_logger.log(LOG_INFO, 0, message);
+    return true;
+}
+
+
+/**
+ * Writes an error.
+ *
+ * @param message   message to show
+ * @return          false (for chaining)
+ */
+bool RplTest::error(const char* format, ...){
+    m_errors++;
+    va_list ap;
+    va_start(ap, format);
+    m_logger.log(LOG_ERROR, 0, format, ap);
+    va_end(ap);
+    return false;
+}
+
+/**
+ * Returns the name of a directory in the temp dir.
+ *
+ * If the named directory does not exist it will be created.
+ *
+ * @param node          NULL or the node (name without path)
+ * @param parent        NULL or a node of the parent
+ * @param withSeparator true: the result ends with slash/backslash
+ * @return              the name of a existing directory
+ */
+QByteArray RplTest::getTempDir(const char* node, const char* parent,
+        bool withSeparator){
+    QByteArray temp("c:\\temp");
+    struct stat info;
+    const char* ptr;
+    if ( (ptr = getenv("TMP")) != NULL )
+        temp = ptr;
+    else if ( (ptr = getenv("TEMP")) != NULL )
+        temp = ptr;
+    else if (stat("/tmp", &info) == 0)
+        temp = "/tmp";
+    char sep = m_separator = temp.indexOf('/') >= 0 ? '/' : '\\';
+    if (temp.at(temp.length() - 1) != sep)
+        temp += sep;
+    if (parent != NULL){
+        temp += parent;
+        if (stat(temp.constData(), &info) != 0)
+            mkdir(temp.constData(), (-1));
+        temp += sep;
+    }
+    if (node != NULL){
+        temp += node;
+        temp += sep;
+        if (stat(temp.data(), &info) != 0)
+            mkdir(temp.data(), -1);
+    }
+    if (! withSeparator)
+        temp.resize(temp.length() - 1);
+    return temp;
+}
+
+/**
+ * Returns a name of a file in a temporary directory.
+ *
+ * @param node              the file's name without path
+ * @param parent            NULL or the name of a subdirectory the file will be inside
+ * @param deleteIfExists    true: if the file exists it will be removed
+ * @return                  the full name of a temporary file
+ */
+QByteArray RplTest::getTempFile(const char* node, const char* parent,
+      bool deleteIfExists){
+    QByteArray dir = getTempDir(parent);
+    QByteArray rc = dir + m_separator + node;
+    struct stat info;
+    if (deleteIfExists && stat(rc.constData(), &info) == 0)
+        unlink(rc.constData());
+    return rc;
+}
diff --git a/rplqt/core/rpltest.hpp b/rplqt/core/rpltest.hpp
new file mode 100644 (file)
index 0000000..18e4540
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef RPLTEST_HPP
+#define RPLTEST_HPP
+
+// the sources generated from QT include this file directly:
+#ifndef RPLCORE_HPP
+#include <QByteArray>
+#endif
+
+class RplTest
+{
+public:
+    RplTest(const char* name);
+    virtual ~RplTest();
+public:
+    bool assertEquals(int expected, int current, const char* file, int lineNo);
+    bool assertEquals(const char* expected, const char* current, const char* file, int lineNo);
+    bool assertEquals(const QByteArray& expected, const QByteArray& current, const char* file, int lineNo);
+    bool assertTrue(bool condition, const char* file, int lineNo);
+    bool assertFalse(bool condition, const char* file, int lineNo);
+    bool assertNull(const void* ptr, const char* file, int lineNo);
+    bool assertNotNull(const void* ptr, const char* file, int lineNo);
+    bool log(const char* message);
+    bool error(const char* message, ...);
+    QByteArray getTempDir(const char* node, const char* parent = NULL);
+
+    void run();
+    virtual void doIt() = 0;
+
+protected:
+    int m_errors;
+    QByteArray m_name;
+    RplLogger m_logger;
+};
+#define check(expected, current) assertEquals(expected, current, __FILE__, __LINE__)
+#define checkT(current) assertTrue(current, __FILE__, __LINE__)
+#define checkF(current) assertTrue(current, __FILE__, __LINE__)
+#define checkN(current) assertNull(current, __FILE__, __LINE__)
+#define checkNN(current) assertNotNull(current, __FILE__, __LINE__)
+
+#endif // RPLTEST_HPP
diff --git a/rplqt/util/rpltcpserver.cpp b/rplqt/util/rpltcpserver.cpp
new file mode 100644 (file)
index 0000000..5c67dbf
--- /dev/null
@@ -0,0 +1,82 @@
+#include "rplutil.hpp"
+
+/**
+ * Constructor.
+ *
+ * @param socketDescriptor  socket of the connection to handle
+ * @param threadId          an unique id for the thread
+ * @param handler           does the work
+ */
+RplTcpThread::RplTcpThread(qintptr socketDescriptor, int threadId, RplTaskHandler* handler) :
+    m_threadId(threadId),
+    m_taskHandler(handler),
+    m_socketDescriptor(socketDescriptor)
+{
+}
+/**
+ * Destructor.
+ */
+RplTcpThread::~RplTcpThread(){
+
+}
+
+void RplTcpThread::run(){
+    QTcpSocket tcpSocket;
+    if (!tcpSocket.setSocketDescriptor(getSocketDescriptor())) {
+            emit error(tcpSocket.error());
+    } else {
+        while(m_taskHandler->handle(&tcpSocket)){
+            // do nothing
+        }
+        tcpSocket.disconnectFromHost();
+        tcpSocket.waitForDisconnected();
+   }
+}
+
+/**
+ * Returns the thread id.
+ *
+ * @return the thread id
+ */
+int RplTcpThread::getThreadId() const{
+    return m_threadId;
+}
+/**
+ * Returns the task handler
+ * @return the task handler
+ */
+RplTaskHandler* RplTcpThread::getTaskHandler() const{
+    return m_taskHandler;
+}
+/**
+ * Returns the tcp socket of the served connection.
+ *
+ * @return the socket
+ */
+qintptr RplTcpThread::getSocketDescriptor() const{
+    return m_socketDescriptor;
+}
+/**
+ * Constructor.
+ *
+ * @param taskHandler   this handler reads from the tcp and interprets the content
+ * @param parent        NULL or the parent which deletes the childen
+ */
+RplTcpServer::RplTcpServer(RplTaskHandler* taskHandler, QObject *parent) :
+    QTcpServer(parent),
+    m_taskHandler(taskHandler),
+    m_threadId(0)
+{
+}
+
+/**
+ * The slot handling a new tcp connection.
+ *
+ * @param socketDescriptor  the tcp socket
+ */
+void RplTcpServer::incomingConnection(qintptr socketDescriptor)
+{
+    RplTcpThread *thread = createThread(socketDescriptor, ++m_threadId, m_taskHandler);
+    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
+    thread->start();
+}
diff --git a/rplqt/util/rpltcpserver.hpp b/rplqt/util/rpltcpserver.hpp
new file mode 100644 (file)
index 0000000..812cd63
--- /dev/null
@@ -0,0 +1,100 @@
+#ifndef RPLTCPSERVER_HPP
+#define RPLTCPSERVER_HPP
+
+// the sources generated from QT include this file directly:
+#ifndef RPLCORE_HPP
+#include <string.h>
+#include <QAbstractSocket>
+#include <QThread>
+#include <QTcpServer>
+#include <QTcpSocket>
+#endif
+
+class RplToServer{
+public:
+    RplToServer() {
+        memset(this, 0, sizeof *this);
+    }
+    unsigned short m_application;
+    unsigned short m_command;
+    unsigned long m_size;
+};
+class RplToClient{
+public:
+    RplToClient() {
+        memset(this, 0, sizeof *this);
+    }
+    unsigned short m_application;
+    unsigned short m_command;
+    unsigned long m_size;
+};
+
+
+class RplTaskHandler
+{
+public:
+    RplTaskHandler();
+    virtual ~RplTaskHandler();
+public:
+
+    bool handle(QAbstractSocket* socket);
+    virtual bool process(char application, int command, int size, const void* params,
+        int answerCode, int& answerSize, void*& answerData) = 0;
+    void setThreadId(int id);
+    int getThreadId();
+private:
+    int m_threadId;
+};
+
+/**
+ * Serves one connection of a multihreaded TCP server.
+ *
+ * Note: The real thing is done by the RplTaskHandler instance.
+ */
+class RplTcpThread : public QThread {
+    Q_OBJECT
+public:
+    RplTcpThread(qintptr socketDescriptor, int threadId, RplTaskHandler* handler);
+    virtual ~RplTcpThread();
+    void run();
+    int getThreadId() const;
+    RplTaskHandler* getTaskHandler() const;
+    qintptr getSocketDescriptor() const;
+
+signals:
+    void error(QTcpSocket::SocketError socketError);
+
+private:
+    // a unique id for the thread
+    int m_threadId;
+    // this handler interprets the info from the TCP connection
+    RplTaskHandler* m_taskHandler;
+    // the assigned socket
+    qintptr m_socketDescriptor;
+};
+
+/**
+ * Implements a multithreaded TCP server.
+ */
+class RplTcpServer : public QTcpServer
+{
+    Q_OBJECT
+public:
+    explicit RplTcpServer(RplTaskHandler* taskHandler, QObject *parent = 0);
+public:
+    bool handleTask();
+    /**
+     * Creates a thread derived from RplTcpThread.
+     * @return  a new thread
+     */
+    virtual RplTcpThread* createThread(qintptr socketDescriptor, int threadId, RplTaskHandler* taskHandler) = 0;
+
+protected slots:
+       void incomingConnection(qintptr socketDescriptor);
+
+private:
+     RplTaskHandler* m_taskHandler;
+     int m_threadId;
+};
+
+#endif // RPLTCPSERVER_HPP
diff --git a/rplqt/util/rplutil.hpp b/rplqt/util/rplutil.hpp
new file mode 100644 (file)
index 0000000..bd6454e
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef RPLUTIL_HPP
+#define RPLUTIL_HPP
+
+#include <QThread>
+#include <QAbstractSocket>
+#include <QTcpSocket>
+
+#include <QTcpServer>
+#include <QThread>
+
+#include "rpltcpserver.hpp"
+#endif // RPLUTIL_HPP