From e67caffff20a4f9cdb0873835833dd5f6670b55f Mon Sep 17 00:00:00 2001 From: hama Date: Fri, 10 Apr 2015 21:17:13 +0200 Subject: [PATCH] refind: tab widget at the top --- appl/refind/mainwindow.ui | 527 ++++-- base/ReByteStorage.cpp | 165 ++ base/ReByteStorage.hpp | 46 + base/ReCharPtrMap.cpp | 66 + base/ReCharPtrMap.hpp | 38 + base/ReConfig.cpp | 192 ++ base/ReConfig.hpp | 40 + base/ReConfigurator.hpp | 21 + base/ReContainer.cpp | 457 +++++ base/ReContainer.hpp | 84 + base/ReException.cpp | 216 +++ base/ReException.hpp | 54 + base/ReLogger.cpp | 635 +++++++ base/ReLogger.hpp | 171 ++ base/ReQStringUtil.cpp | 618 ++++++ base/ReQStringUtil.hpp | 81 + base/ReStringUtil.cpp | 539 ++++++ base/ReStringUtil.hpp | 40 + base/ReTerminator.cpp | 80 + base/ReTerminator.hpp | 32 + base/ReTest.cpp | 530 ++++++ base/ReTest.hpp | 80 + base/ReWriter.cpp | 192 ++ base/ReWriter.hpp | 59 + base/rebase.hpp | 57 + base/testrplexample.cpp | 55 + bench/.gitignore | 8 + bench/mk_bench.pl | 228 +++ cunit/allTests.cpp | 81 + cunit/cuReASTree.cpp | 104 + cunit/cuReBench.cpp | 63 + cunit/cuReByteStorage.cpp | 78 + cunit/cuReCharPtrMap.cpp | 36 + cunit/cuReConfig.cpp | 62 + cunit/cuReContainer.cpp | 59 + cunit/cuReEnigma.cpp | 115 ++ cunit/cuReException.cpp | 53 + cunit/cuReLexer.cpp | 282 +++ cunit/cuReMFParser.cpp | 228 +++ cunit/cuReMatrix.cpp | 374 ++++ cunit/cuReQStringUtil.cpp | 184 ++ cunit/cuReSource.cpp | 110 ++ cunit/cuReStringUtil.cpp | 197 ++ cunit/cuReTraverser.cpp | 155 ++ cunit/cuReVM.cpp | 74 + cunit/cuReWriter.cpp | 50 + cunit/unittests.pro | 49 + expr/ReASClasses.cpp | 1102 +++++++++++ expr/ReASClasses.hpp | 210 ++ expr/ReASTree.cpp | 3477 ++++++++++++++++++++++++++++++++++ expr/ReASTree.hpp | 720 +++++++ expr/ReLexer.cpp | 1178 ++++++++++++ expr/ReLexer.hpp | 243 +++ expr/ReMFParser.cpp | 1252 ++++++++++++ expr/ReMFParser.hpp | 156 ++ expr/ReParser.cpp | 249 +++ expr/ReParser.hpp | 63 + expr/ReSource.cpp | 822 ++++++++ expr/ReSource.hpp | 227 +++ expr/ReVM.cpp | 456 +++++ expr/ReVM.hpp | 112 ++ expr/reexpr.hpp | 30 + math/ReEnigma.cpp | 287 +++ math/ReEnigma.hpp | 69 + math/ReMatrix.cpp | 490 +++++ math/ReMatrix.hpp | 105 + math/ReRandom.cpp | 132 ++ math/ReRandom.hpp | 36 + math/remath.hpp | 20 + net/ReNetConfig.cpp | 18 + net/ReNetConfig.hpp | 22 + net/ReTCPClient.cpp | 152 ++ net/ReTCPClient.hpp | 75 + net/ReTCPPeer.cpp | 359 ++++ net/ReTCPPeer.hpp | 85 + net/ReTCPServer.cpp | 228 +++ net/ReTCPServer.hpp | 121 ++ net/renet.hpp | 27 + os/ReTraverser.cpp | 989 ++++++++++ os/ReTraverser.hpp | 290 +++ os/reos.hpp | 48 + remodules.hpp | 35 + static/ReStaticLib.cpp | 16 + static/getsrc.pl | 29 + static/rplstatic.pro | 103 + test/mfparser/baseTest.txt | 9 + test/mfparser/field1.txt | 22 + test/mfparser/forC1.txt | 23 + test/mfparser/forC2.txt | 17 + test/mfparser/forIt1.txt | 20 + test/mfparser/ifTest1.txt | 40 + test/mfparser/ifTest2.txt | 19 + test/mfparser/list1.txt | 12 + test/mfparser/list2.txt | 20 + test/mfparser/main1.txt | 26 + test/mfparser/map1.txt | 12 + test/mfparser/map2.txt | 21 + test/mfparser/meth1.txt | 11 + test/mfparser/meth2.txt | 36 + test/mfparser/meth3.txt | 32 + test/mfparser/meth4.txt | 59 + test/mfparser/methc1.txt | 25 + test/mfparser/opTest1.txt | 49 + test/mfparser/repeatTest.txt | 20 + test/mfparser/string1.mf | 3 + test/mfparser/string1.txt | 24 + test/mfparser/varDefTest.txt | 22 + test/mfparser/whileTest.txt | 21 + 108 files changed, 22039 insertions(+), 202 deletions(-) create mode 100644 base/ReByteStorage.cpp create mode 100644 base/ReByteStorage.hpp create mode 100644 base/ReCharPtrMap.cpp create mode 100644 base/ReCharPtrMap.hpp create mode 100644 base/ReConfig.cpp create mode 100644 base/ReConfig.hpp create mode 100644 base/ReConfigurator.hpp create mode 100644 base/ReContainer.cpp create mode 100644 base/ReContainer.hpp create mode 100644 base/ReException.cpp create mode 100644 base/ReException.hpp create mode 100644 base/ReLogger.cpp create mode 100644 base/ReLogger.hpp create mode 100644 base/ReQStringUtil.cpp create mode 100644 base/ReQStringUtil.hpp create mode 100644 base/ReStringUtil.cpp create mode 100644 base/ReStringUtil.hpp create mode 100644 base/ReTerminator.cpp create mode 100644 base/ReTerminator.hpp create mode 100644 base/ReTest.cpp create mode 100644 base/ReTest.hpp create mode 100644 base/ReWriter.cpp create mode 100644 base/ReWriter.hpp create mode 100644 base/rebase.hpp create mode 100644 base/testrplexample.cpp create mode 100644 bench/.gitignore create mode 100644 bench/mk_bench.pl create mode 100644 cunit/allTests.cpp create mode 100644 cunit/cuReASTree.cpp create mode 100644 cunit/cuReBench.cpp create mode 100644 cunit/cuReByteStorage.cpp create mode 100644 cunit/cuReCharPtrMap.cpp create mode 100644 cunit/cuReConfig.cpp create mode 100644 cunit/cuReContainer.cpp create mode 100644 cunit/cuReEnigma.cpp create mode 100644 cunit/cuReException.cpp create mode 100644 cunit/cuReLexer.cpp create mode 100644 cunit/cuReMFParser.cpp create mode 100644 cunit/cuReMatrix.cpp create mode 100644 cunit/cuReQStringUtil.cpp create mode 100644 cunit/cuReSource.cpp create mode 100644 cunit/cuReStringUtil.cpp create mode 100644 cunit/cuReTraverser.cpp create mode 100644 cunit/cuReVM.cpp create mode 100644 cunit/cuReWriter.cpp create mode 100644 cunit/unittests.pro create mode 100644 expr/ReASClasses.cpp create mode 100644 expr/ReASClasses.hpp create mode 100644 expr/ReASTree.cpp create mode 100644 expr/ReASTree.hpp create mode 100644 expr/ReLexer.cpp create mode 100644 expr/ReLexer.hpp create mode 100644 expr/ReMFParser.cpp create mode 100644 expr/ReMFParser.hpp create mode 100644 expr/ReParser.cpp create mode 100644 expr/ReParser.hpp create mode 100644 expr/ReSource.cpp create mode 100644 expr/ReSource.hpp create mode 100644 expr/ReVM.cpp create mode 100644 expr/ReVM.hpp create mode 100644 expr/reexpr.hpp create mode 100644 math/ReEnigma.cpp create mode 100644 math/ReEnigma.hpp create mode 100644 math/ReMatrix.cpp create mode 100644 math/ReMatrix.hpp create mode 100644 math/ReRandom.cpp create mode 100644 math/ReRandom.hpp create mode 100644 math/remath.hpp create mode 100644 net/ReNetConfig.cpp create mode 100644 net/ReNetConfig.hpp create mode 100644 net/ReTCPClient.cpp create mode 100644 net/ReTCPClient.hpp create mode 100644 net/ReTCPPeer.cpp create mode 100644 net/ReTCPPeer.hpp create mode 100644 net/ReTCPServer.cpp create mode 100644 net/ReTCPServer.hpp create mode 100644 net/renet.hpp create mode 100644 os/ReTraverser.cpp create mode 100644 os/ReTraverser.hpp create mode 100644 os/reos.hpp create mode 100644 remodules.hpp create mode 100644 static/ReStaticLib.cpp create mode 100644 static/getsrc.pl create mode 100644 static/rplstatic.pro create mode 100644 test/mfparser/baseTest.txt create mode 100644 test/mfparser/field1.txt create mode 100644 test/mfparser/forC1.txt create mode 100644 test/mfparser/forC2.txt create mode 100644 test/mfparser/forIt1.txt create mode 100644 test/mfparser/ifTest1.txt create mode 100644 test/mfparser/ifTest2.txt create mode 100644 test/mfparser/list1.txt create mode 100644 test/mfparser/list2.txt create mode 100644 test/mfparser/main1.txt create mode 100644 test/mfparser/map1.txt create mode 100644 test/mfparser/map2.txt create mode 100644 test/mfparser/meth1.txt create mode 100644 test/mfparser/meth2.txt create mode 100644 test/mfparser/meth3.txt create mode 100644 test/mfparser/meth4.txt create mode 100644 test/mfparser/methc1.txt create mode 100644 test/mfparser/opTest1.txt create mode 100644 test/mfparser/repeatTest.txt create mode 100644 test/mfparser/string1.mf create mode 100644 test/mfparser/string1.txt create mode 100644 test/mfparser/varDefTest.txt create mode 100644 test/mfparser/whileTest.txt diff --git a/appl/refind/mainwindow.ui b/appl/refind/mainwindow.ui index 508205f..0c7a9a3 100644 --- a/appl/refind/mainwindow.ui +++ b/appl/refind/mainwindow.ui @@ -18,229 +18,352 @@ - - - - - - - true - - - - - - - - 200 - 16777215 - - - - File Patterns: - - - - - - - - 200 - 16777215 - - - - Text Pattern: - - - - - - - - 300 - 0 - - - - true - - - - - - - + + + + 1 + 1 + + + + + 16777215 + 130 + + + + 0 + + + + Directory, File Patterns, Text Pattern + + + + + + + + Search + + + Ctrl+F + + + + + + + true + + + + + + + + + + 50 + 16777215 + + + + Up + + + + + + + + 50 + 16777215 + + + + ... + + + + + + + - 50 + 200 16777215 - Up + Directory: + + + + + + + + 300 + 0 + + + + true - - + + - 50 + 200 16777215 - ... + File Patterns: + + + + + + + ignore case + + + + + 200 + 16777215 + + + + Text Pattern: + + + + + + + true + + + + + + + Exclude Patterns: + + + + + + + true + + + + + + + + + Dirs + + + + + + + Files + + + + + + + Links + + + + + + + Hidden + + + + + + + Specials + + + + + - - - - Search - - - Ctrl+F - - - - - - - ignore case - - - - - - - - 200 - 16777215 - - - - Directory: - - - - - - - - 200 - 16777215 - - - - true - - - - - - - - 200 - 16777215 - - - - true - - - - - - - - 200 - 16777215 - - - - Younger than: - - - - - - - - 150 - 16777215 - - - - Min. Size: - - - - - - - - 150 - 16777215 - - - - Max Size - - - - - - - - 200 - 16777215 - - - - true - - - - - - - - 200 - 16777215 - - - - true - - - - - - - - 200 - 16777215 - - - - Older than: - - - - - - - true - - - - - + + + + Size, Date, Depth + + + + + 0 + 0 + 412 + 94 + + + + + + + + 150 + 16777215 + + + + Max Size + + + + + + + + 200 + 16777215 + + + + true + + + + + + + + 150 + 16777215 + + + + Min. Size: + + + + + + + + 200 + 16777215 + + + + true + + + + + + + + 200 + 16777215 + + + + Older than: + + + + + + + + 200 + 16777215 + + + + true + + + + + + + + 200 + 16777215 + + + + true + + + + + + + + 200 + 16777215 + + + + Younger than: + + + + + + + Min. Depth + + + + + + + true + + + + + + + Max. Depth + + + + + + + true + + + + + + + + + + diff --git a/base/ReByteStorage.cpp b/base/ReByteStorage.cpp new file mode 100644 index 0000000..644c7c2 --- /dev/null +++ b/base/ReByteStorage.cpp @@ -0,0 +1,165 @@ +/* + * ReByteStorage.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +/** @file + * + * @brief A very efficient storage for bytes and C strings. + */ +/** @file rplcore/rplbytestorage.hpp + * + * @brief Definitions for a very efficient storage for bytes and C strings. + */ + +#include "base/rebase.hpp" +/** @class ReByteStorage ReByteStorage.hpp "base/ReByteStorage.hpp" + * + * @brief Implements a very efficient byte storage. + * + * Efficiency: Allocation of one block needs mostly only 1 comparison + * and 2 assignments. + * + * Restriction: The blocks can be returned (freed) only all together, not block by block. + * This can be an advantage! + * + * Process: + * The storage manages large buffers. Allocation can be done only in the + * last buffer. If the buffer has too little space for the new block a new + * buffer will be allocated and linked into the buffer list. + * One buffer can store dozens or hundreds of blocks. Therefore allocation and + * freeing is much cheeper than allocation by new(). + */ + +/** + * @brief Constructor. + * + * @param bufferSize the size of one buffer + */ +ReByteStorage::ReByteStorage(int bufferSize) : + m_bufferSize(bufferSize), + m_buffer(NULL), + m_rest(0), + m_freePosition(NULL), + m_summarySize(0), + m_buffers(0){ +} + +/** + * @brief Destructor. + */ +ReByteStorage::~ReByteStorage(){ + const uint8_t* ptr = m_buffer; + while (ptr != NULL){ + const uint8_t* old = ptr; + ptr = *(const uint8_t**) (ptr); + delete[] old; + m_buffers--; + } + assert(m_buffers == 0); +} + +/** + * @brief Allocates a block in a new allocated buffer. + * + * This method will be called if the buffer has too little space. + * A new buffer will be allocated and the block will be allocated + * in this new block. + * + * @note The block address is returned, but the allocation must be done outside! + * + * @param size of the new block (inclusive the trailing '\0') + * @return a new block with the size bytes + */ +char* ReByteStorage::allocBuffer(int size){ + m_rest = size + sizeof(uint8_t*) <= (size_t) m_bufferSize ? + m_bufferSize : size + sizeof(uint8_t*); + m_summarySize += m_rest; + m_buffers++; + uint8_t* rc = new uint8_t[m_rest]; + *(uint8_t**) rc = m_buffer; + m_buffer = rc; + rc += sizeof(uint8_t*); + // the block allocation will be done outside! + m_freePosition = rc; + m_rest -= sizeof(uint8_t*); + return reinterpret_cast (rc); +} + +/** + * @brief Duplicates a string into a new allocated block. + * + * @param source the source string + * @param size the length of the string. + * If < 0 strlen(source) will be used + * @return a copy of the source string. The copy ends always with '\0' + */ +const char* ReByteStorage::allocateChars(const char* source, int size){ + if (size < 0) + size = strlen(source); + const char* rc = allocateChars(size + 1); + memcpy((void*) rc, source, size); + ((char*) rc)[size] = '\0'; + return rc; +} + +/** + * @brief Duplicates a string into a new allocated block. + * + * The unicode string will be converted into an UTF-8 string. + * + * @param source the source string + * @return a copy of the source string. The copy ends always with '\0' + */ +const char* ReByteStorage::allocUtf8(const ReString& source){ + const char* rc = allocateChars(source.toUtf8().constData()); + return rc; +} + +/** + * @brief Allocates a byte block without initialization. + * + * @param size the size of the block to allocate + * + * @return a byte block (without a trailing '\0') + */ +uint8_t* ReByteStorage::allocateBytes(int size){ + uint8_t* rc = + size <= m_rest ? + m_freePosition : reinterpret_cast (allocBuffer(size)); + m_freePosition += size; + m_rest -= size; + return rc; +} + +/** + * @brief Allocates a byte block initialized by '\0'. + * + * @param size the size of the block to allocate + * + * @return a byte block (without a trailing '\0') + */ +uint8_t* ReByteStorage::allocateZeros(int size){ + uint8_t* rc = allocateBytes(size); + memset(rc, 0, size); + return rc; +} + +/** + * @brief Copy a byte block to a new allocated byte block. + * + * @param source the source to copy + * @param size the size of the block to allocate + * @return a byte block (without a trailing '\0') + */ +uint8_t* ReByteStorage::allocateBytes(void* source, int size){ + uint8_t* rc = allocateBytes(size); + memcpy(rc, source, size); + return rc; +} diff --git a/base/ReByteStorage.hpp b/base/ReByteStorage.hpp new file mode 100644 index 0000000..e54fb96 --- /dev/null +++ b/base/ReByteStorage.hpp @@ -0,0 +1,46 @@ +/* + * ReByteStorage.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +#ifndef RECHARSTORAGE_HPP +#define RECHARSTORAGE_HPP + +class ReByteStorage { +public: + ReByteStorage(int blockSize); + ~ReByteStorage(); +public: + char* allocBuffer(int size); + /** + * @brief Allocates a char block. + * + * @return a new block + */ + inline char* allocateChars(int size){ + char* rc = size <= m_rest ? (char*) m_freePosition : allocBuffer(size); + m_freePosition += size; + m_rest -= size; + return rc; + } + const char* allocateChars(const char* source, int size = -1); + const char* allocUtf8(const ReString& source); + uint8_t* allocateBytes(int size); + uint8_t* allocateZeros(int size); + uint8_t*allocateBytes(void* source, int size); +private: + int m_bufferSize; + uint8_t* m_buffer; + int m_rest; + uint8_t* m_freePosition; + int64_t m_summarySize; + int m_buffers; +}; + +#endif // RECHARSTORAGE_HPP diff --git a/base/ReCharPtrMap.cpp b/base/ReCharPtrMap.cpp new file mode 100644 index 0000000..c898af5 --- /dev/null +++ b/base/ReCharPtrMap.cpp @@ -0,0 +1,66 @@ +/* + * ReCharPtrMap.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +#include "base/rebase.hpp" + +/** @class ReKeyCharPtr ReCharPtrMap.hpp "base/ReCharPtrMap.hpp" + * + * @brief Allows using char* pointers as key in QMap. + * + * The template QMap uses the operator < to search in the map. + * But the char* pointers have no such an operator. + * The solution is the class RplMapCharPtr which implements + * this operator. + * + * strcmp() is used to implement the '<' operator. + */ + +/** + * @brief Constructor. + * + * @param ptr the key used in the map. + * @note the pointer will be stored in the map as a key, + * not the content + */ +ReKeyCharPtr::ReKeyCharPtr(const char* ptr) : + m_ptr(ptr){ +} + +/** @class ReCharPtrMap ReCharPtrMap.hpp "base/ReCharPtrMap.hpp" + * + * @brief A template for a map using const char* as keys. + * + * The value type is dynamic (a parameter type of the template). + * + * Usage: + *

+ * ReCharPtrMap ids;
+ * if (! id.contains("jonny"))
+ *    ids["jonny"] = 1;
+ * 
+ * + * Important:
+ * Keys used with this class must be unchangable and must live during the + * whole life of the map. + * + * Wrong example: + *

+ * ReCharPtrMap ids;
+ * void init(int keyNo, int value){
+ *    char key[10];
+ *    qsnprintf(buffer, sizeof buffer, "key%d", keyNo);
+ *    ids[key] = value;
+ * }
+ * init(1, 100);
+ * 
+ * The lifetime of the key is the body of the function init(). + * The key becomes invalid after the call. + */ diff --git a/base/ReCharPtrMap.hpp b/base/ReCharPtrMap.hpp new file mode 100644 index 0000000..6a7b515 --- /dev/null +++ b/base/ReCharPtrMap.hpp @@ -0,0 +1,38 @@ +/* + * ReCharPtrMap.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +#ifndef RECHARPTRMAP_HPP +#define RECHARPTRMAP_HPP + +class ReKeyCharPtr { + friend bool operator <(ReKeyCharPtr const& op1, ReKeyCharPtr const& op2); +public: + ReKeyCharPtr(const char* ptr); +private: + const char* m_ptr; +}; +/** + * @brief Compares two instances of the class ReKeyCharPtr. + * @param op1 1st operand + * @param op2 2nd operand + * @return true: op1 < op2
+ * false: op1 >= op2 + */ +inline bool operator <(ReKeyCharPtr const& op1, ReKeyCharPtr const& op2){ + bool rc = strcmp(op1.m_ptr, op2.m_ptr) < 0; + return rc; +} + +template +class ReCharPtrMap: public QMap { +}; + +#endif // RECHARPTRMAP_HPP diff --git a/base/ReConfig.cpp b/base/ReConfig.cpp new file mode 100644 index 0000000..9962b2b --- /dev/null +++ b/base/ReConfig.cpp @@ -0,0 +1,192 @@ +/* + * ReConfig.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#include "base/rebase.hpp" + +/** @file + * + * @brief Reading/writing configuration files. + */ +/** @file rplcore/rplconfig.hpp + * + * @brief Definitions for reading/writing configuration files. + */ + +/** @class ReConfig ReConfig.hpp "base/ReConfig.hpp" + * + * @brief Imports and exports a configuration file into a QHash instance. + * + * The format of the file:
+ * DEFS or COMMENTS + * + * DEFS ::= KEY=VALUE + * + * KEY is a string starting with an alphanumeric character and does not contain a '=' + * + * VALUE is a string + */ + +enum Locations { + LOC_WRITE_1 = LOC_FIRST_OF(LOC_CONFIG), // 10201 + 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 + */ +ReConfig::ReConfig(const char* file, bool readOnly, ReLogger* 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. + */ +ReConfig::~ReConfig(){ + if (m_ownLogger) + delete m_logger; + m_logger = NULL; +} + +/** + * Inititializes a logger. + */ +void ReConfig::initLogger(){ + m_logger = new ReLogger(); + ReMemoryAppender* appender = new ReMemoryAppender(); + appender->setAutoDelete(true); + m_logger->addAppender(appender); + + ReStreamAppender* appender2 = new ReStreamAppender(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 ReConfig::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 ReConfig::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 ReConfig::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
+ * false: error occurred + */ +bool ReConfig::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
+ * false: error occurred + */ +bool ReConfig::write(const char* file){ + bool rc = false; + if (m_readOnly) + m_logger->log(LOG_ERROR, LOC_WRITE_1, "cannot write: (readonly"); + else{ + m_logger->logv(LOG_ERROR, LOC_WRITE_2, "not implemented: write(%s)", + file); + } + return rc; +} + diff --git a/base/ReConfig.hpp b/base/ReConfig.hpp new file mode 100644 index 0000000..d022162 --- /dev/null +++ b/base/ReConfig.hpp @@ -0,0 +1,40 @@ +/* + * ReConfig.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#ifndef RECONFIG_HPP +#define RECONFIG_HPP + +class ReConfig: public ReConfigurator, public QHash { +public: + ReConfig(const char* file = NULL, bool readOnly = true, ReLogger* logger = + NULL); + virtual ~ReConfig(); + +public: + bool read(const char* file); + bool write(const char* file); + void clear(); + const QList & getLines() const; + + virtual bool asBool(const char* key, bool defaultValue) const; + virtual int asInt(const char* key, int defaultValue) const; + virtual QByteArray asString(const char* key, const char* defaultValue); +private: + void initLogger(); +private: + const char* m_file; + QList m_lineList; + bool m_readOnly; + ReLogger* m_logger; + // true: the logger must be destroyed in the destructor + bool m_ownLogger; +}; + +#endif // RECONFIG_HPP diff --git a/base/ReConfigurator.hpp b/base/ReConfigurator.hpp new file mode 100644 index 0000000..49adc42 --- /dev/null +++ b/base/ReConfigurator.hpp @@ -0,0 +1,21 @@ +/* + * ReConfigurator.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#ifndef RECONFIGURATOR_HPP +#define RECONFIGURATOR_HPP + +class ReConfigurator { +public: + virtual int asInt(const char* key, int defaultValue) const = 0; + virtual bool asBool(const char* key, bool defaultValue) const = 0; + virtual QByteArray asString(const char* key, const char* defaultValue) = 0; +}; + +#endif // RECONFIGURATOR_HPP diff --git a/base/ReContainer.cpp b/base/ReContainer.cpp new file mode 100644 index 0000000..67a439a --- /dev/null +++ b/base/ReContainer.cpp @@ -0,0 +1,457 @@ +/* + * ReContainer.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#include "base/rebase.hpp" + +/** @file + * @brief Implements a portable data container. + */ + +/** @file recore/ReContainer.hpp + * + * @brief Definition for a portable data container. + */ + +/** @class ReContainer ReContainer.hpp "base/ReContainer.hpp" + * + * @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
+ * list_of_bag ::= bag1 bag2 ...
+ * bag_descr ::= bag_type1 bag_type2 ... ':'
+ * bag_types := i(nteger) ' ' | s(tring) | b(indata255) | B(indata64k) X(blob4G)
+ * item_list ::= item1 item2...
+ * 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 09 36[2]cis:!7b Nirwana <0> Y -ab34 A long string with an trailing '0' <0>
+ * 
+ * magic header: Rpl&1 length: 09h data length: 46h bag count: 2 item types: char int string + * + */ + +enum { + // 11000 + LOC_FILL_1 = LOC_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* ReContainer::MAGIC_1 = "Rpl&1"; + +/** + * @brief Constructor. + * + * @param sizeHint Probable length of the container + */ +ReContainer::ReContainer(size_t sizeHint) : + m_data(""), + m_countBags(0), + m_typeList(""), + m_ixItem(0), + m_ixBag(0), + m_readPosition(NULL){ + if (sizeHint > 0) + m_data.reserve(sizeHint); +} + +/** + * @brief Destructor. + */ +ReContainer::~ReContainer(){ +} + +/** + * @brief Adds an type to the type list. + * + * @param tag the type tag + */ +void ReContainer::addType(type_tag_t tag){ + if (m_countBags == 0) + startBag(); + if (m_countBags == 1) + m_typeList.append((char) tag); +} + +/** + * @brief Starts a new bag. + */ +void ReContainer::startBag(){ + m_countBags++; + m_ixBag = 0; +} +/** + * @brief Adds a character to the current bag. + * + * @param value value to insert + */ +void ReContainer::addChar(char value){ + addType(TAG_CHAR); + //if (m_typeList.at(m_ixBag) != TAG_INT) + // ReLogger::logAndThrow(LOG_ERROR, __FILE__, __LINE__, 1, "unexpected type: %c instead of c", m_typeList.at(m_ixBag)); + m_data.append(value); +} +/** + * @brief Adds an integer to the current bag. + * + * @param value value to add + */ +void ReContainer::addInt(int value){ + addType(TAG_INT); + char buffer[64]; + char* ptr = buffer; + if (value < 0){ + *ptr++ = '-'; + value = -value; + } + qsnprintf(ptr, sizeof buffer - 1, "%x ", value); + m_data.append(buffer); +} +/** + * @brief Adds an integer to the current bag. + * + * @param value value to add + */ +void ReContainer::addInt(int64_t value){ + addType(TAG_INT); + char buffer[128]; + qsnprintf(buffer, sizeof buffer, "%llx ", value); + m_data.append(buffer); +} + +/** + * @brief Adds a string to the current bag.n + * + * @param value value to add + */ +void ReContainer::addString(const char* value){ + addType(TAG_STRING); + // store with trailing '\0' + m_data.append(value, strlen(value) + 1); +} +/** + * @brief Adds binary data to the current bag. + * + * @param value binary data + * @param size size of the binary data in bytes + */ +void ReContainer::addData(uint8_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); + +} + +/** + * @brief Returns the container byte buffer. + * + * @return the container as a byte array + */ +const QByteArray& ReContainer::getData(){ + if (m_typeList.length() != 0){ + char buffer[128]; + // RPL&1 0a b5[2]cis: !12 + qsnprintf(buffer, sizeof buffer, "%x[%d]%s:", + (unsigned int) m_data.length(), m_countBags, m_typeList.data()); + char header[128 + 8]; + qsnprintf(header, sizeof header, "%s%02x%s", MAGIC_1, + (unsigned int) strlen(buffer), buffer); + m_data.insert(0, header); + } + return m_data; +} + +/** + * @brief Fills the container with an byte array. + * + * @param data the container as a byte array + */ +void ReContainer::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 = (uint8_t*) end + 1; + +} +/** + * @brief Returns the number of bags in the container. + * + * @return the number of bags + */ +int ReContainer::getCountBags() const{ + return m_countBags; +} +/** + * @brief Sets the begin of the new bag. + */ +void ReContainer::nextBag(){ + if (m_ixItem < m_typeList.length() && m_ixItem != -1) + throw ReException(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 ReException(LOG_ERROR, LOC_NEXT_BAG_2, NULL, "no more bags: %d", + m_ixBag); +} +/** + * @brief Sets the next item. + * + * @param expected the expected data type + */ +void ReContainer::nextItem(type_tag_t expected){ + if (m_ixBag < 0){ + m_ixBag = 0; + m_ixItem = 0; + } + if (m_ixItem >= m_typeList.length()) + throw ReException(LOG_ERROR, LOC_NEXT_ITEM_1, ReLogger::globalLogger(), + "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 ReException(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 > (uint8_t*) (m_data.data() + m_data.length())) + throw ReException(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()); +} + +/** + * @brief Reads the next character from the current item in the current bag. + * + * @return the next char from the container + */ +char ReContainer::nextChar(){ + nextItem(TAG_CHAR); + char rc = *m_readPosition++; + return rc; +} + +/** + * @brief Reads the next integer from the current item in the current bag. + * + * @return the next integer from the container + */ +int ReContainer::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", m_readPosition, 16); + m_readPosition = (uint8_t*) strchr((const char*) m_readPosition, ' ') + 1; + if (isNegativ) + value = -value; + return value; +} +/** + * @brief Reads the next integer from the current item in the current bag. + * + * @return the next integer from the container + */ +int64_t ReContainer::nextInt64(){ + nextItem(TAG_INT); + bool isNegativ = *m_readPosition == '-'; + if (isNegativ) + m_readPosition++; + uint64_t value = 0; + if (sscanf((const char*) m_readPosition, "%llx ", &value) != 1) + throw RplInvalidDataException(LOG_ERROR, LOC_NEXT_INT_1, + "not a hex_number", m_readPosition, 16); + m_readPosition = (uint8_t*) strchr((const char*) m_readPosition, ' ') + 1; + if (isNegativ) + value = -value; + return (int64_t) value; +} + +/** + * @brief Reads the next string from the current item in the current bag. + * + * @return the next '\0' delimited string from the container + */ +const char* ReContainer::nextString(){ + nextItem(TAG_STRING); + const char* rc = (const char*) m_readPosition; + m_readPosition += strlen(rc) + 1; + return rc; +} + +/** + * @brief 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
+ * false: data contains the item data only + * @return the size of the read data + */ +size_t ReContainer::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; +} + +/** + * @brief Dumps a container as a human readable string. + * + * @param title will be used in the first line + * @param maxBags if there are more bags they will be ignored + * @param maxStringLength if strings are longer the will be cut + * @param maxBlobLength maximum count of bytes which will be dumped + * @param separatorItems separator between two items, e.g. '\\n' or '|' + * @return a human readable string describing the container + */ +QByteArray ReContainer::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(ReStringUtil::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(ReStringUtil::toNumber(ixBag)).append(":\n"); + nextBag(); + QByteArray item; + int maxLength; + for (int ixItem = 0; 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(ReStringUtil::toNumber(iValue)).append( + " / "); + rc.append(ReStringUtil::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(ReStringUtil::toNumber(item.length())).append("] "); + maxLength = + item.length() < maxBlobLength ? + item.length() : maxBlobLength; + rc.append(ReStringUtil::hexDump(item.data(), maxLength, 16)).append( + separatorItems); + break; + default: + break; + } + } + } + + // restore the current state: + m_ixBag = safeIxBag; + m_ixItem = safeIxItem; + return rc; +} + diff --git a/base/ReContainer.hpp b/base/ReContainer.hpp new file mode 100644 index 0000000..8d1dcc2 --- /dev/null +++ b/base/ReContainer.hpp @@ -0,0 +1,84 @@ +/* + * ReContainer.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#ifndef RECONTAINER_HPP +#define RECONTAINER_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#include +#endif +class ReContainer { +public: + typedef enum { + + TAG_CHAR = 'c', ///< one character + TAG_INT = 'i', ///< an integer number, up to 64 bit + TAG_STRING = 's', ///< a string ending with a '\\0' + TAG_DATA255 = 'd', ///< binary data, up to 255 bytes long + TAG_DATA64K = 'D', ///< binary data, up to 64 KiBytes long + TAG_DATA4G = 'X', ///< binary data, up to 4 GiBytes long + TAG_CONTAINER = '!' ///< a container (recursion) + } type_tag_t; + static const char* MAGIC_1; +public: + ReContainer(size_t sizeHint); + virtual ~ReContainer(); +private: + // No copy constructor: no implementation! + ReContainer(const ReContainer& source); + // Prohibits assignment operator: no implementation! + ReContainer& operator =(const ReContainer& source); +public: + // Building the container: + void addType(type_tag_t tag); + void startBag(); + void addChar(char cc); + void addInt(int value); + void addInt(int64_t 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(); + int64_t 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 // RECONTAINER_HPP diff --git a/base/ReException.cpp b/base/ReException.cpp new file mode 100644 index 0000000..024acbd --- /dev/null +++ b/base/ReException.cpp @@ -0,0 +1,216 @@ +/* + * ReException.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +/** @mainpage + * + * @note A real public library for QT. + * + * This library contains the module groups + *
    + *
  • rplcore: basic definitions, used in all other module groups
  • + *
  • rplmath: mathematic definitions and tools
  • + *
  • rplexpr: definition for parsing and interpretation of languages
  • + *
  • rplnet: definitions and tools for tcp/udp communication
  • + *
+ * + * Each module group has a central include file, which organizes the necessary + * include files. You had to include only the central include file. + * + * Example: + *

+ * #include "base/rebase.hpp"
+ * #include "expr/reexpr.hpp"
+ * 
+ * In this case all definitions of rplcore and rplexpr are available. + */ +/** @file + * @brief Generally usable exceptions. + */ +/** @file rplcore/rplexception.hpp + * + * @brief Definitions for a generally usable exceptions. + */ +#include "base/rebase.hpp" + +enum { + LOC_NOT_IMPLEMENTED_1 = LOC_FIRST_OF(LOC_EXCEPTION), +}; +/** @class ReException ReException.hpp "base/ReException.hpp" + * + * @brief A generally usable exception with or without logging. + * + * Note: If the logger is not given by parameter + * the usage of the global logger is not threadsafe. + */ +class ReException; + +/** + * @brief Constructor. + * + * For derived classes only! + */ +ReException::ReException() : + m_message(""){ +} + +/** + * @brief Constructor. + * + * @param format the reason of the exception + * @param ... the values for the placeholders in the format. + */ +ReException::ReException(const char* format, ...) : + m_message(""){ + char buffer[64000]; + va_list ap; + va_start(ap, format); + qvsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + m_message = buffer; +} + +/** + * @brief Constructor. + * + * This constructor automatically logs the given data. + * + * @param level the logging level, e.g. LOG_ERROR + * @param location an unique identifier for the location + * where the exception was thrown + * @param format the reason of the exception. + * Can contain placeholders (@see + * std::printf()) + * @param ... the values of the placeholders + * in format + * @param logger if NULL the global logger will be used + */ +ReException::ReException(ReLoggerLevel level, int location, ReLogger* logger, + const char* format, ...) : + m_message(""){ + char buffer[64000]; + va_list ap; + va_start(ap, format); + qvsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + m_message = buffer; + if (logger == NULL) + logger = ReLogger::globalLogger(); + logger->log(level, location, buffer); +} + +/** @class RplRangeException rplexception.hpp "rplcore/rplexception.hpp" + * + * @brief An exception for integer range errors. + * + * The error will be logged. + * + * Note: If the logger is not given by parameter + * the usage of the global logger is not threadsafe. + */ + +/** + * @brief Constructor. + * + * This exception can be used if a value does not be + * inside a given range. + * + * This constructor automatically logs the given data. + * + * @param level the logging level, e.g. LOG_ERROR + * @param location an unique identifier for the location + * where the exception was thrown + * @param current the current value + * @param lbound the minimum of the allowed values + * @param ubound the maximum of the allowed values + * @param message the reason. If NULL a generic + * message will be used + * @param logger if NULL the global logger will be used + */ + +ReRangeException::ReRangeException(ReLoggerLevel level, int location, + size_t current, size_t lbound, size_t ubound, const char* message, + ReLogger* logger) : + ReException(""){ + char buffer[64000]; + if (message == NULL) + message = "value outside limits"; + qsnprintf(buffer, sizeof buffer, "%s: %lu [%lu, %lu]", + message == NULL ? "" : message, current, lbound, ubound); + if (logger == NULL) + logger = ReLogger::globalLogger(); + logger->log(level, location, buffer); +} + +/** @class RplInvalidDataException rplexception.hpp "rplcore/rplexception.hpp" + * + * @brief An exception usable if binary data have the wrong structure. + * + * The data will be dumped as hex and ASCII dump. + * + * Note: If the logger is not given by parameter + * the usage of the global logger is not threadsafe. + */ + +/** + * @brief Constructor. + * + * This exception can be used if data does not have a given fomat. + * + * This constructor automatically logs the given data. This data + * will be dumped (hexadecimal dump and ASCII interpretation). + * + * @param level the logging level, e.g. LOG_ERROR + * @param location an unique identifier for the location + * where the exception was thrown + * @param message the reason + * @param data pointer to binary data + * @param dataSize the size of the data which should be dumped + * @param logger if NULL the global logger will be used + */ +RplInvalidDataException::RplInvalidDataException(ReLoggerLevel level, + int location, const char* message, const void* data, size_t dataSize, + ReLogger* logger) : + ReException(""){ + 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++){ + qsnprintf(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 = ReLogger::globalLogger(); + logger->log(level, location, buffer); +} + +/** + * @brief Constructor. + * + * @param message describes what is not implemented + */ +ReNotImplementedException::ReNotImplementedException(const char* message) : + ReException("not implemented: ", message){ + ReLogger::globalLogger()->log(LOG_ERROR, LOC_NOT_IMPLEMENTED_1, + getMessage()); +} diff --git a/base/ReException.hpp b/base/ReException.hpp new file mode 100644 index 0000000..03d3909 --- /dev/null +++ b/base/ReException.hpp @@ -0,0 +1,54 @@ +/* + * ReException.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#ifndef REEXCEPTION_HPP +#define REEXCEPTION_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#endif + +class ReException { +protected: + ReException(); +public: + ReException(const char* message, ...); + ReException(ReLoggerLevel level, int location, const char* message, + ReLogger* logger = NULL); + ReException(ReLoggerLevel level, int location, ReLogger* logger, + const char* message, ...); + const QByteArray& getMessage() const{ + return m_message; + } +protected: + QByteArray m_message; +}; + +class ReRangeException: public ReException { +public: + ReRangeException(ReLoggerLevel level, int location, size_t current, + size_t lbound, size_t ubound, const char* message = NULL, + ReLogger* logger = NULL); +}; + +class RplInvalidDataException: public ReException { +public: + RplInvalidDataException(ReLoggerLevel level, int location, + const char* message, const void* data = NULL, size_t dataSize = 0, + ReLogger* logger = NULL); +}; + +class ReNotImplementedException: public ReException { +public: + ReNotImplementedException(const char* message); +}; + +#endif // REEXCEPTION_HPP diff --git a/base/ReLogger.cpp b/base/ReLogger.cpp new file mode 100644 index 0000000..777b0aa --- /dev/null +++ b/base/ReLogger.cpp @@ -0,0 +1,635 @@ +/* + * ReLogger.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +/** @file + * A configurable logger for different output media. + */ +/** @file rplcore/rpllogger.hpp + * + * Definitions for a configurable logger for different output media. + */ +#include "base/rebase.hpp" +#include +#include + +enum { + LOC_ADD_APPENDER_1 = LOC_FIRST_OF(LOC_LOGGER), // 10101 +}; + +ReLogger* ReLogger::m_globalLogger = NULL; + +/** + * @brief Returns the global logger. + * + * If it does not exist it will be created (singleton). + * + * @return the global logger + */ +ReLogger* ReLogger::globalLogger(){ + if (m_globalLogger == NULL){ + m_globalLogger = new ReLogger(); + m_globalLogger->buildStandardAppender("globallogger"); + } + return m_globalLogger; +} +/** + * @brief Frees the resources of the global logger. + */ +void ReLogger::destroyGlobalLogger(){ + delete m_globalLogger; + m_globalLogger = NULL; +} + +/** @class ReAppender rpllogger.hpp "rplcore/rpllogger.hpp" + * + * @brief Puts the logging info to a medium (e.g. a file). + * + * This is an abstract base class. + */ + +/** + * @brief Constructor. + * + * @param name identifies the logger. Useful for ReLogger::findLogger() + */ +ReAppender::ReAppender(const QByteArray& name) : + m_name(name), m_level(LOG_INFO){ + +} +/** + * @brief Destructor. + */ +ReAppender::~ReAppender(){ +} + +/** + * Returns the name. + * + * @return the name of the instance + */ +const char* ReAppender::getName() const{ + return m_name.data(); +} + +/** + * @brief Sets the level. + * + * @param level + */ +void ReAppender::setLevel(ReLoggerLevel level){ + m_level = level; +} +/** + * @brief Returns the level. + * + * @return the level + */ +ReLoggerLevel ReAppender::getLevel() const{ + return m_level; +} +/** + * @brief 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 ReAppender::isActive(ReLoggerLevel level){ + return level <= m_level; +} + +/** + * @brief Sets or clears the automatic deletions. + * + * @param onNotOff the state of the auto deletion + */ +void ReAppender::setAutoDelete(bool onNotOff){ + m_autoDelete = onNotOff; +} + +/** + * @brief Returns the state of the auto deletion. + * + * @return true: the logger destroys the instance + */ +bool ReAppender::isAutoDelete() const{ + return m_autoDelete; +} + +/** @class ReLogger rpllogger.hpp "rplcore/rpllogger.hpp" + * + * @brief Implements a logger. + * + * The logger takes the call from the calling location. + * But the output assumes the class ReAppender, + * more exactly: a subclass from the abstract class + * ReAppender, + * + * For single threaded applications there is a possability of + * a global logger. In this case the logger can be got with the static + * method ReLogger::globalLogger(). + * + * Note: using the global logger is not threadsafe! + * + * Each call of the logger should be provided by a unique identifier + * named the location. This allows to find the error quickly. + */ + +/** + * @brief Constructor. + */ +ReLogger::ReLogger() : + // m_appenders(), + m_countAppenders(0), + m_stdPrefix(), + m_mutex(), + m_withLocking(false){ + memset(m_appenders, 0, sizeof m_appenders); +} + +/** + * @brief Destructor. + */ +ReLogger::~ReLogger(){ + for (size_t ix = 0; ix < m_countAppenders; ix++){ + ReAppender* appender = m_appenders[ix]; + if (appender->isAutoDelete()){ + delete appender; + } + m_appenders[ix] = NULL; + } +} +/** + * @brief Returns the first char of a logging line displaying the logging level. + * + * @param level the level to "convert" + * @return the assigned prefix char + */ +char ReLogger::getPrefixOfLevel(ReLoggerLevel 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; +} + +/** + * @brief Tests whether at least one appender is active for a given level. + * + * @param level level to test + * @return false: all appenders are not activated by this level
+ * true: otherwise + */ +bool ReLogger::isActive(ReLoggerLevel level) const{ + bool rc = false; + for (size_t ix = 0; ix < m_countAppenders; ix++){ + ReAppender* appender = m_appenders[ix]; + if (appender->isActive(level)){ + rc = true; + break; + } + } + return rc; +} + +/** + * @brief Sets the log level for all appenders. + * + * @param level level to set + */ +void ReLogger::setLevel(ReLoggerLevel level){ + for (size_t ix = 0; ix < m_countAppenders; ix++){ + ReAppender* appender = m_appenders[ix]; + appender->setLevel(level); + } +} + +/** + * @brief Sets or clears the state "with locking". + * + * @param onNotOff true: the logger is thread save.
+ * false: not thread save + */ +void ReLogger::setWithLocking(bool onNotOff){ + m_withLocking = onNotOff; +} + +/** + * @brief 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 location an unique identifier of the location + * @return the standard logging line prefix + */ +const QByteArray& ReLogger::getStdPrefix(ReLoggerLevel level, int location){ + if (m_stdPrefix.isEmpty()) + m_stdPrefix = buildStdPrefix(level, location); + return m_stdPrefix; +} + +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @return true: for chaining + */ +bool ReLogger::log(ReLoggerLevel level, int location, const char* message){ + m_stdPrefix = ""; + bool first = true; + for (size_t ix = 0; ix < m_countAppenders; ix++){ + ReAppender* appender = m_appenders[ix]; + if (appender->isActive(level)){ + if (first && m_withLocking) + m_mutex.lock(); + appender->log(level, location, message, this); + } + } + if (!first && m_withLocking) + m_mutex.unlock(); + return true; +} +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @return true: for chaining + */ +bool ReLogger::log(ReLoggerLevel level, int location, + const QByteArray& message){ + return log(level, location, message.data()); +} + +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @return true: for chaining + */ +bool ReLogger::log(ReLoggerLevel level, int location, const ReString& message){ + return log(level, location, message.toUtf8().data()); +} + +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @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 ReLogger::logv(ReLoggerLevel level, int location, const char* format, ...){ + char buffer[64000]; + va_list ap; + va_start(ap, format); + qvsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + return log(level, location, buffer); +} + +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @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 ReLogger::logv(ReLoggerLevel level, int location, const QByteArray& format, + ...){ + char buffer[64000]; + va_list ap; + va_start(ap, format); + qvsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + return log(level, location, buffer); +} + +/** + * @brief Logs (or not) the calling location. + * + * @param level the level of the location + * @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 ReLogger::log(ReLoggerLevel level, int location, const char* format, + va_list& varlist){ + char buffer[64000]; + qvsnprintf(buffer, sizeof buffer, format, varlist); + return log(level, location, buffer); +} + +/** + * @brief Builds the standard prefix of a logging line. + * + * @param level the level of the location + * @param location an unique identifier of the location + */ +QByteArray ReLogger::buildStdPrefix(ReLoggerLevel level, int location){ + time_t now = time(NULL); + struct tm* now2 = localtime(&now); + char buffer[64]; + qsnprintf(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); +} + +/** + * @brief Adds an appender. + * + * @param appender appender to add + */ +void ReLogger::addAppender(ReAppender* 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"); + } +} + +/** + * @brief Returns the appender with a given name. + * + * @param name the appender's name + * + * @return NULL: no appender with this name is registered
+ * otherwise: the wanted appender + */ +ReAppender* ReLogger::findAppender(const char* name) const{ + ReAppender* rc = NULL; + for (size_t ix = 0; ix < m_countAppenders; ix++){ + ReAppender* current = m_appenders[ix]; + if (strcmp(name, current->getName()) == 0){ + rc = current; + break; + } + } + return rc; +} + +/** + * @brief Builds the standard appender configured by a configuration file. + * + * @param config configuration file + * @param prefix the prefix of the key in the config file + * (in front of "name") + * @param defaultLogfilePrefix + * the prefix of the log file if no entry in the + * configuration file + */ +void ReLogger::buildStandardAppender(ReConfig* config, const char* prefix, + const char* defaultLogfilePrefix){ + QByteArray sPrefix(prefix); + QByteArray logFilePrefix = config->asString(sPrefix + "name", + defaultLogfilePrefix); + + int maxSize = config->asInt(+"maxsize", 10100100); + int maxCount = config->asInt(sPrefix + "maxfiles", 5); + buildStandardAppender(logFilePrefix, maxSize, maxCount); + QByteArray sLevel = config->asString(sPrefix + "level", "info"); + ReLoggerLevel level = LOG_INFO; + if (strcasecmp(sLevel.constData(), "error") == 0) + level = LOG_ERROR; + else if (strcasecmp(sLevel, "warning") == 0) + level = LOG_WARNING; + else if (strcasecmp(sLevel, "debug") == 0) + level = LOG_DEBUG; + setLevel(level); +} + +/** + * @brief 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 ReLogger::buildStandardAppender(const QByteArray& prefix, int maxSize, + int maxCount){ + ReStreamAppender* streamAppender = new ReStreamAppender(stderr); + streamAppender->setAutoDelete(true); + addAppender((ReAppender*) streamAppender); + ReFileAppender* fileAppender = new ReFileAppender(prefix, maxSize, maxCount); + fileAppender->setAutoDelete(true); + addAppender((ReAppender*) fileAppender); +} + +/** @class ReStreamAppender rpllogger.hpp "rplcore/rpllogger.hpp" + * + * @brief Puts the logging info to a standard output stream. + * + * The possible streams are std::stdout or std::stderr + */ + +/** + * @brief Constructor. + */ +ReStreamAppender::ReStreamAppender(FILE* file, const char* appenderName) : + ReAppender(QByteArray(appenderName)), m_fp(file){ +} + +/** + * @brief Destructor. + */ +ReStreamAppender::~ReStreamAppender(){ + fflush(m_fp); +} + +/** + * @brief 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 ReStreamAppender::log(ReLoggerLevel level, int location, + const char* message, ReLogger* 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" + +/** @class ReFileAppender rpllogger.hpp "rplcore/rpllogger.hpp" + * + * @brief Puts the logging info to a file. + * + * The appender creates a collection of files to limit the used disk space. + * Each logfile is limited to a given size. And the number of files is limited. + * If the count exceeds the oldest file will be deleted. + * + * Each logfile's name has a given name prefix, a running number + * and the suffix ".log", e.g. "globallogger.003.log". + */ + +/** + * @brief 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 ReLogger::findAppender() + */ +ReFileAppender::ReFileAppender(const QByteArray& prefix, int maxSize, + int maxCount, const char* appenderName) : + ReAppender(QByteArray(appenderName)), + m_prefix(prefix), + m_maxSize(maxSize), + m_maxCount(maxCount), + m_currentSize(0), + m_currentNo(0), + m_fp(NULL){ + open(); +} + +/** + * @brief Destructor. + */ +ReFileAppender::~ReFileAppender(){ + if (m_fp != NULL){ + fclose(m_fp); + m_fp = NULL; + } +} + +/** + * @brief Opens the next log file. + */ +void ReFileAppender::open(){ + if (m_fp != NULL) + fclose(m_fp); + char fullName[512]; + qsnprintf(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; + } +} + +/** + * @brief 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 ReFileAppender::log(ReLoggerLevel level, int location, const char* message, + ReLogger* 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" + +/** @class ReMemoryAppender rpllogger.hpp "rplcore/rpllogger.hpp" + * + * @brief Puts the logging info to an internal buffer. + * + * This line list can be required: getLines(). + */ + +/** + * @brief 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 + */ +ReMemoryAppender::ReMemoryAppender(int maxLines, const char* appenderName) : + ReAppender(appenderName), + m_lines(), + m_maxLines(maxLines), + m_addPrefix(true){ + m_lines.reserve(maxLines); +} + +/** + * @brief Destructor. + */ +ReMemoryAppender::~ReMemoryAppender(){ +} + +/** + * 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 ReMemoryAppender::log(ReLoggerLevel level, int location, + const char* message, ReLogger* logger){ + if (m_lines.size() >= m_maxLines) + m_lines.removeFirst(); + if (!m_addPrefix) + m_lines.append(message); + else{ + QByteArray msg(logger->getStdPrefix(level, location)); + msg += message; + m_lines.append(msg); + } +} +#pragma GCC diagnostic warning "-Wunused-parameter" + +/** + * @brief Returns the list of lines. + * + * @return the line list + */ +const QList & ReMemoryAppender::getLines() const{ + return m_lines; +} + +/** + * @brief Deletes all log lines. + */ +void ReMemoryAppender::clear(){ + m_lines.clear(); +} diff --git a/base/ReLogger.hpp b/base/ReLogger.hpp new file mode 100644 index 0000000..310eb47 --- /dev/null +++ b/base/ReLogger.hpp @@ -0,0 +1,171 @@ +/* + * ReLogger.hpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ +#ifndef RELOGGER_HPP +#define RELOGGER_HPP + +/** + * + */ +class ReLogger; +class ReConfig; +/** + * @brief 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 ReLoggerLevel { + LOG_ERROR = 10, ///< marks an error. + LOG_WARNING = 15, ///< marks a warning + LOG_INFO = 20, ///< marks an information + LOG_DEBUG = 25 ///< for debug purpose only +}; + +class ReAppender { +public: + ReAppender(const QByteArray& name); + virtual ~ReAppender(); +private: + // No copy constructor: no implementation! + ReAppender(const ReAppender& source); + // Prohibits assignment operator: no implementation! + ReAppender& operator =(const ReAppender& source); +public: + virtual void log(ReLoggerLevel level, int location, const char* message, + ReLogger* logger) = 0; + bool isActive(ReLoggerLevel level); + void setLevel(ReLoggerLevel level); + void setAutoDelete(bool onNotOff); + bool isAutoDelete() const; + ReLoggerLevel 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 + ReLoggerLevel m_level; + // true: the logger destroys the instance. false: the deletion must be done outside of the logger + bool m_autoDelete; +}; + +class ReLogger { +public: + static ReLogger* globalLogger(); + static void destroyGlobalLogger(); +private: + // the standard logger, can be called (with globalLogger()) from each location + static ReLogger* m_globalLogger; +public: + ReLogger(); + virtual ~ReLogger(); +private: + // No copy constructor: no implementation! + ReLogger(const ReLogger& source); + // Prohibits assignment operator: no implementation! + ReLogger& operator =(const ReLogger& source); +public: + bool log(ReLoggerLevel level, int location, const char* message); + bool log(ReLoggerLevel level, int location, const QByteArray& message); + bool log(ReLoggerLevel level, int location, const ReString& message); + bool logv(ReLoggerLevel level, int location, const char* format, ...); + bool logv(ReLoggerLevel level, int location, const QByteArray& format, ...); + bool log(ReLoggerLevel level, int location, const char* format, + va_list& varlist); + void addAppender(ReAppender* appender); + ReAppender* findAppender(const char* name) const; + void buildStandardAppender(ReConfig* config, const char* prefix = "logfile.", + const char* defaultLoggerName = "logger"); + void buildStandardAppender(const QByteArray& prefix, + int maxSize = 10 * 1024 * 1024, int maxCount = 5); + QByteArray buildStdPrefix(ReLoggerLevel level, int location); + const QByteArray& getStdPrefix(ReLoggerLevel level, int location); + char getPrefixOfLevel(ReLoggerLevel level) const; + bool isActive(ReLoggerLevel level) const; + void setLevel(ReLoggerLevel level); + void setWithLocking(bool onNotOff); +private: + // the assigned appenders: + ReAppender* 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; + QMutex m_mutex; + bool m_withLocking; +}; + +/** + * Implements an appender which puts the messages to a standard stream: stdout or stderr + */ +class ReStreamAppender: public ReAppender { +public: + ReStreamAppender(FILE* stream, const char* appenderName = "FileAppender"); + virtual ~ReStreamAppender(); +public: + virtual void log(ReLoggerLevel level, int location, const char* message, + ReLogger* logger); +private: + // stdout or stderr: + FILE* m_fp; +}; + +/** + * Implements an appender which puts the messages to a file + */ +class ReFileAppender: public ReAppender { +public: + ReFileAppender(const QByteArray& name, int maxSize, int maxCount, + const char* appenderName = "FileAppender"); + virtual ~ReFileAppender(); +public: + void open(); + virtual void log(ReLoggerLevel level, int location, const char* message, + ReLogger* logger); + +private: + // prefix of the log file name. Will be appended by "..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 ReMemoryAppender: public ReAppender { +public: + ReMemoryAppender(int maxLines = 1024, const char* appenderName = + "MemoryAppender"); + ~ReMemoryAppender(); +public: + virtual void log(ReLoggerLevel level, int location, const char* message, + ReLogger* logger); + const QList & getLines() const; + void clear(); +private: + QList m_lines; + // maximum count of m_lines. If larger the oldest lines will be deleted. + int m_maxLines; + // true: standard prefix (level + datetime) will be stored too. + bool m_addPrefix; +}; + +#endif // RELOGGER_HPP diff --git a/base/ReQStringUtil.cpp b/base/ReQStringUtil.cpp new file mode 100644 index 0000000..2f85a64 --- /dev/null +++ b/base/ReQStringUtil.cpp @@ -0,0 +1,618 @@ +/* + * ReQStringUtil.cpp + * + * License: Public Domain + * You can use and modify this file without any restriction. + * Do what you want. + * No warranties and disclaimer of any damages. + * You also can use this license: http://www.wtfpl.net + * The latest sources: https://github.com/republib + */ + +/** @file + * @brief Missed operation for ReStrings. + */ +/** @file rplcore/rplqstring.hpp + * + * @brief Definitions for missed operation for ReStrings. + */ +#include "base/rebase.hpp" +#include +#include +/** + * @brief Determines the length and vlaue of an integer. + * + * @param text the number as text + * @param start the first index to inspect + * @param radix the base of the number sytem: 8 (octal), 10 or 16 + * @param pValue OUT: the value of the integer. May be NULL + * + * @return <=0: no integer found + * otherwise: the length of the integer + */ +int ReQStringUtil::lengthOfUInt64(const ReString& text, int start, int radix, + quint64* pValue){ + int inputLength = text.size(); + int64_t value = 0; + int ix = start; + int cc; + if (radix == 10){ + while (ix < inputLength){ + if ((cc = text[ix].unicode()) >= '0' && cc <= '9') + value = value * 10 + cc - '0'; + else + break; + ix++; + } + }else if (radix == 16){ + while (ix < inputLength){ + if ((cc = text[ix].unicode()) >= '0' && cc <= '9') + value = value * 16 + cc - '0'; + else if (cc >= 'A' && cc <= 'F') + value = value * 16 + cc - 'A' + 10; + else if (cc >= 'a' && cc <= 'f') + value = value * 16 + cc - 'a' + 10; + else + break; + ix++; + } + }else if (radix == 8){ + while (ix < inputLength){ + if ((cc = text[ix].unicode()) >= '0' && cc <= '7') + value = value * 8 + cc - '0'; + else + break; + ix++; + } + }else{ + throw ReException("ReQStringUtil::lengthOfInt(): wrong radix: %d", radix); + } + if (pValue != NULL) + *pValue = value; + return ix - start; +} +/** + * @brief Determines the length and value of an unsigned integer. + * + * @param text the number as text + * @param start the first index to inspect + * @param radix the base of the number sytem: 8 (octal), 10 or 16 + * @param pValue OUT: the value of the integer. May be NULL + * + * @return 0: no integer found + * otherwise: the length of the integer + */ +int ReQStringUtil::lengthOfUInt(const ReString& text, int start, int radix, + uint* pValue){ + quint64 value; + int rc = lengthOfUInt64(text, start, radix, &value); + if (pValue != NULL) + *pValue = (uint) value; + return rc; +} + +/** + * Skips a character in a text at a given position if it has an expected value. + * + * @param text text to inspect + * @param expected the character which is expected + * @param index IN/OUT: the position of the expected character. + * Will be incremented if the expected character is found + * @param length IN/OUT: IN: 0: do nothing
+ * OUT: 0: the expected character was not found. + * otherwise: the length is incremented + */ +void ReQStringUtil::skipExpected(const ReString& text, QChar expected, + int& index, int& length){ + if (length == 0){ + // error state, do nothing + } else if (index >= text.length() || text[index] != expected){ + length = 0; + } else { + index++; + length++; + } +} + +/** + * Returns the length of a date in a string. + * + * The syntax of a time is 'dd.mm.yyyy' or 'yyyy.mm.dd'. + * + * @param text text to inspect + * @param start the first index in text to inspect + * @param value OUT: the value of the found date. Not changed if result is 0.
+ * May be NULL + * @return 0: no date found
+ * otherwise: the length of the date in the string + */ +int ReQStringUtil::lengthOfDate(const ReString& text, int start, QDate* value) +{ + uint day = 0; + uint month = 0; + uint year = 0; + int length = lengthOfUInt(text, start, 10, &year); + switch(length){ + case 1: + case 2: + day = year; + year = 0; + break; + case 4: + break; + default: + length = 0; + break; + } + int length2; + start += length; + skipExpected(text, '.', start, length); + if (length > 0){ + length2 = lengthOfUInt(text, start, 10, &month); + if (length2 < 1 || length2 > 2) + length = 0; + else { + start += length2; + length += length2; + } + } + skipExpected(text, '.', start, length); + if (year > 0){ + length2 = lengthOfUInt(text, start, 10, &day); + if (length2 < 1 || length2 > 2) + length = 0; + else { + start += length2; + length += length2; + } + } else{ + length2 = lengthOfUInt(text, start, 10, &year); + if (length2 != 4) + length = 0; + else { + start += length2; + length += length2; + } + } + if (day < 1 || day > 31 || month < 1 || month > 12 || year < 1970 || year > 2100) + length = 0; + if (length != 0 && value != NULL) + *value = QDate(year, month, day); + return length; +} + +/** + * Returns the length of a date and/or time in a string. + * + * @param text text to inspect + * @param start the first index in text to inspect + * @param allowDateOnly false: if the date is not followed by + * a time the result will be 0 + * @param allowTimeOnly false: if no date is found at the given + * text position the result will be 0 + * @param value the value of the found date. Not changed if result is 0.
+ * May be NULL + * @return 0: no date found
+ * otherwise: the length of the date in the string + */ +int ReQStringUtil::lengthOfDateTime(const ReString& text, int start, + bool allowDateOnly, bool allowTimeOnly, QDateTime* value) +{ + QDate date; + QTime time; + int length = lengthOfDate(text, start, &date); + if (length == 0){ + if (allowTimeOnly){ + date = QDate::currentDate(); + length = lengthOfTime(text, start, &time); + } + } else { + if (start + length + 1 + 3 <= text.length()) { + start += length; + int length2 = 0; + if (! text[start].isDigit()){ + QTime time2; + length2 = lengthOfTime(text, start + 1, &time2); + if (length2 == 0 && ! allowDateOnly) + length = 0; + else if (length2 > 0){ + length += 1 + length2; + time = time2; + } + } + } + } + if (length > 0 && value != NULL) + *value = QDateTime(date, time); + return length; +} +/** + * Returns the length of a time in a string. + * + * The syntax of a time is hh:mm[:ss] + * + * @param text text to inspect + * @param start the first index in text to inspect + * @param value OUT: the value of the found time. Not changed if result is 0.
+ * May be NULL + * @return 0: no date found
+ * otherwise: the length of the date in the string + */ +int ReQStringUtil::lengthOfTime(const ReString& text, int start, QTime* value) +{ + uint hour = 0; + uint minute = 0; + uint sec = 0; + int length = lengthOfUInt(text, start, 10, &hour); + if (length > 0 && hour > 23) + length = 0; + if (length > 0){ + start += length; + } + int length2; + skipExpected(text, ':', start, length); + if (length > 0){ + length2 = lengthOfUInt(text, start, 10, &minute); + if (length2 < 1 || length2 > 2 || minute >= 60) + length = 0; + else + start += length2, length += length2; + } + if (length > 0 && start < text.length() && text[start] == ':'){ + length++; + start++; + length2 = lengthOfUInt(text, start, 10, &sec); + if (length2 < 1 || length2 > 2 || sec >= 60) + length = 0; + else + start += length2, length += length2; + } + if (length != 0 && value != NULL) + *value = QTime(hour, minute, sec); + return length; +} + +/** + * @brief Determines the length and value of a floting point number. + * + * @param text the number as text + * @param start the first index to inspect + * @param pValue OUT: the value of the integer. May be NULL + * + * @return <=0: no real number found + * otherwise: the length of the floating point number + */ +int ReQStringUtil::lengthOfReal(const ReString& text, int start, qreal* pValue){ + int inputLength = text.size(); + qreal value = 0.0; + int cc; + int ix = start; + while (ix < inputLength){ + if ((cc = text[ix].unicode()) >= '0' && cc <= '9') + value = value * 10 + (cc - '0'); + else + break; + ix++; + } + // found: a digit has been found (in front of or behind the '.' + bool found = ix > start; + if (ix < inputLength && text[ix].unicode() == '.'){ + ix++; + } + if (ix < inputLength && text[ix].isDigit()){ + found = true; + qreal divisor = 1; + qreal precision = 0; + while (ix < inputLength && (cc = text[ix].unicode()) >= '0' && cc <= '9'){ + divisor *= 10; + precision = precision * 10 + cc - '0'; + ix++; + } + value += precision / divisor; + }else if (!found){ + ix = start; + } + if (found && ix + 1 < inputLength && toupper(text[ix].unicode()) == 'E'){ + int savePoint = ix; + ix++; + bool negative = false; + if ((cc = text[ix].unicode()) == '+') + ix++; + else if (cc == '-'){ + ix++; + negative = true; + } + if (ix >= inputLength || !text[ix].isDigit()) + ix = savePoint; + else{ + int exponent = 0; + while (ix < inputLength && text[ix].isDigit()){ + exponent = exponent * 10 + text[ix].unicode() - '0'; + ix++; + } + if (negative) + value /= qPow(10, exponent); + else + value *= qPow(10, exponent); + } + } + if (pValue) + *pValue = value; + return found ? ix - start : 0; +} + +/** + * @brief Converts a ReString into an utf-8 string + * + * The expression qstring.toUtf8().constData() is not allowed + * in a variable argument list like sprintf. This is a thread save workaround. + * + * @param source string to convert + * @param buffer OUT: target buffer + * @param bufferSize size of the target buffer + * @return buffer + */ +char*ReQStringUtil::utf8(const ReString& source, char buffer[], + size_t bufferSize){ + QByteArray val = source.toUtf8(); + if (val.length() < (int) bufferSize) + bufferSize = val.length() + 1; + memcpy(buffer, val.constData(), bufferSize - 1); + buffer[bufferSize - 1] = '\0'; + return buffer; +} + +class ReParserException : public ReException { +public: + ReParserException(const QString& message) : + ReException(), + m_message(message){ + } +public: + QString m_message; +}; +/** + * Constructor. + * + * @param expr an expression, e.g. "10*1024kByte+5MiByte" + * @param unitList description of the allowed units with its factor
+ * example: "kibyte:1024;kbyte:1000;mibyte:1048576;mbyte:1000000" + */ +ReUnitParser::ReUnitParser(const QString& expr, const char* unitList, bool parseAtOnce) : + m_result(0), m_expr(expr), m_message(), m_unitList(unitList){ + normalize(); + if (parseAtOnce) + parse(); +} + +/** + * Returns the result of the expression as a 64 bit integer. + * + * @param defaultValue the result if the expression was not valid + * @return defaultValue: the result was not valid
+ * the result as a 64 bit integer + */ +int64_t ReUnitParser::asInt64(int64_t defaultValue){ + return m_message.isEmpty() ? m_result : defaultValue; +} +/** + * Returns the result of the expression as an integer. + * + * @param defaultValue the result if the expression was not valid + * @return defaultValue: the result was not valid
+ * the result as an integer + */ +int ReUnitParser::asInt(int defaultValue){ + return m_message.isEmpty() ? (int) m_result : defaultValue; +} +/** + * Returns the result of the expression as floating point number. + * + * @param defaultValue the result if the expression was not valid + * @return defaultValue: the result was not valid
+ * the result as a floating point + */ +real_t ReUnitParser::asReal(real_t defaultValue){ + return m_message.isEmpty() ? (real_t) m_result : defaultValue; +} + +/** + * Returns an empty string or the error message. + * + * @return "": no error occurred
+ * otherwise: the error message + */ +const QString& ReUnitParser::errorMessage() const{ + return m_message; +} + +/** + * Returns whether the given expression is valid. + * + * @return true: the expression is valid, a result was calculated + */ +bool ReUnitParser::isValid() const{ + return m_message.isEmpty(); +} + +/** + * @brief Normalizes the internal stored unit expression. + */ +void ReUnitParser::normalize(){ + // Remove the blanks: + for (int ii = m_expr.length() - 1; ii >= 0; ii--){ + if (m_expr[ii].isSpace()) + m_expr.remove(ii, 1); + } + // Replace the '-' operator by '+' as operator and '-' as sign: + // This makes the syntax easier to parse: only one sum operator ('+'). + for (int ii = m_expr.length() - 1; ii > 0; ii--){ + if (m_expr[ii] == '-' && m_expr[ii -1] != '+' && m_expr[ii - 1] != '*'){ + m_expr.insert(ii, '+'); + } + } +} + +/** + * Evaluate the expression. + */ +void ReUnitParser::parse(){ + QStringList addends = m_expr.split("+"); + QStringList::const_iterator it; + try{ + m_result = 0; + for (it = addends.begin(); it != addends.end(); ++it){ + QStringList factors = it->split("*"); + QStringList::const_iterator it2; + int64_t product = 1; + for (it2 = factors.begin(); it2 != factors.end(); ++it2){ + QStringList powerOperands = it2->split("^"); + if (powerOperands.count() > 2) + throw ReParserException(QObject::tr("more than 2 power operators, e.g. '2^3^4'")); + QStringList::const_iterator it3 = powerOperands.begin(); + QString op = *it3; + bool isNeg = op.startsWith("-"); + if (isNeg) + op = op.mid(1); + uint64_t value = valueOf(op); + if (powerOperands.count() > 1){ + uint64_t fac = value; + uint64_t exponent = valueOf(*++it3); + if (qLn(value) * qLn(exponent) >= qLn(qPow(2.0, 64))) + throw ReParserException(QObject::tr("number overflow while power operation")); + for (int ii = 1; ii < (int) exponent; ii++) + value = value * fac; + } + product *= value; + if (isNeg) + product = -product; + } + m_result += product; + } + + } catch (ReParserException& e){ + m_message = e.m_message; + } +} + +/** + * Calculates the value of a number or a (number, unit) pair. + * + * @param value a non negative number or a number followed by a unit
+ * only units defined in m_unitList are allowed
+ * examples: "4kByte" returns 4000, "4kibyte" returns 4096 + * @return the value of the number multiplied by the factor given by the unit + * @throws ReParserException + */ +uint64_t ReUnitParser::valueOf(const QString& value) const{ + uint64_t rc = 0; + int ix = ReQStringUtil::lengthOfUInt64(value, 0, 10, &rc); + if (ix == 0) + throw ReParserException(QObject::tr("number expected: ") + value); + QString unit = value.mid(ix); + if (! unit.isEmpty()){ + QStringList units = QString(m_unitList).split(";"); + QStringList::const_iterator it; + bool found = false; + for (it = units.begin(); it != units.end(); ++it){ + QStringList pair = it->split(":"); + if (pair.count() == 0) + throw ReParserException(QObject::tr("missing ':' in unit definition, e.g. 'k:1000': ") + *it); + if (pair.count() > 2) + throw ReParserException(QObject::tr("too many ':' in unit definition: ") + *it); + bool ok = false; + QString unit2 = *pair.begin(); + QString factor = *++pair.begin(); + uint64_t nFactor = factor.toLongLong(&ok); + if (! ok) + throw ReParserException(QObject::tr("not a number: ") + factor); + if (unit2.startsWith(unit)){ + rc *= nFactor; + found = true; + break; + } + } + if (! found) + throw ReParserException(QObject::tr("unknown unit, allowed: ") + QString(m_unitList)); + } + return rc; +} + +/** + * Constructor. + * + * @param expr an expression, e.g. "10*1024kByte+5MiByte" + */ +ReSizeParser::ReSizeParser(const QString& expr) : + ReUnitParser(expr, "byte:1;kbyte:1000;kibyte:1024;" + "mbyte:1000000;mibyte:1048576;" + "gbyte:1000000000;gibyte:1073741824;" + "tbyte:1000000000000;tibyte:1099511627776"){ +} +/** + * Constructor. + * + * @param expr an expression, e.g. "3*3days-5min+3weeks" + */ +ReDateTimeParser::ReDateTimeParser(const QString& expr) : + ReUnitParser("", "minutes:60;hours:3600;days:86400;weeks:604800", false){ + parseDateTime(expr); +} + +/** + * Returns the parser result as a QDateTime instance. + * + * @return the parse result. If invalid input the result is the begin of the epoche + */ +QDateTime ReDateTimeParser::asDateTime() const +{ + return m_dateTime; +} + +/** + * Parses a date/time expression. + * + * Syntax: { "now" | [