/* * ebusd - daemon for communication with eBUS heating systems. * Copyright (C) 2014-2021 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LIB_EBUS_FILEREADER_H_ #define LIB_EBUS_FILEREADER_H_ #include #include #include #include #include #include "lib/ebus/symbol.h" #include "lib/ebus/result.h" #include "lib/utils/thread.h" namespace ebusd { /** @file lib/ebus/filereader.h * Helper class and constants for reading configuration files. * * The @a FileReader template class allows to read CSV compliant text files * while splitting each read line into fields. * It also supports special treatment of comment lines starting with a "#", as * well as so called "default values" indicated by the first field starting * with a "*" symbol. */ using std::string; using std::map; using std::ostream; using std::istream; /** the separator character used between fields. */ #define FIELD_SEPARATOR ',' /** the separator character used to quote text having the @a FIELD_SEPARATOR in it. */ #define TEXT_SEPARATOR '"' /** the separator character as string used to quote text having the @a FIELD_SEPARATOR in it. */ #define TEXT_SEPARATOR_STR "\"" /** the separator character used between multiple values (in CSV only). */ #define VALUE_SEPARATOR ';' /** special marker string for skipping columns in @a MappedFileReader. */ static const char SKIP_COLUMN[] = "\b"; /** * An abstract class that support reading definitions from a file. */ class FileReader { public: /** * Constructor. */ FileReader() {} /** * Destructor. */ virtual ~FileReader() {} /** * Open a file as stream for reading. * @param filename the name of the file being read. * @param errorDescription a string in which to store the error description in case of error. * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. * @return the opened @a istream on success, or nullptr on error. */ static istream* openFile(const string& filename, string* errorDescription, time_t* time = nullptr); /** * Read the definitions from a stream. * @param stream the @a istream to read from. * @param filename the relative name of the file being read. * @param mtime a @a time_t value with the modification time of the file. * @param verbose whether to verbosely log problems. * @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults. * @param errorDescription a string in which to store the error description in case of error. * @param replace whether to replace an already existing entry. * @param hash optional pointer to a @a size_t value for storing the hash of the file, or nullptr. * @param size optional pointer to a @a size_t value for storing the normalized size of the file, or nullptr. * @return @a RESULT_OK on success, or an error code. */ virtual result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose, map* defaults, string* errorDescription, bool replace = false, size_t* hash = nullptr, size_t* size = nullptr); /** * Read a single line definition from the stream. * @param stream the @a istream to read from. * @param filename the name of the file being read. * @param verbose whether to verbosely log problems. * @param lineNo the last line number (incremented with each line read). * @param row the definition row to clear and update with the read data (for performance reasons only). * @param errorDescription a string in which to store the error description in case of error. * @param replace whether to replace an already existing entry. * @param hash optional pointer to a @a size_t value for updating with the hash of the line, or nullptr. * @param size optional pointer to a @a size_t value for updating with the normalized length of the line, or nullptr. * @return @a RESULT_OK on success, or an error code. */ virtual result_t readLineFromStream(istream* stream, const string& filename, bool verbose, unsigned int* lineNo, vector* row, string* errorDescription, bool replace, size_t* hash, size_t* size); /** * Add a definition that was read from a file. * @param filename the name of the file being read. * @param lineNo the current line number in the file being read. * @param row the definition row (allowed to be modified). * @param errorDescription a string in which to store the error description in case of error. * @param replace whether to replace an already existing entry. * @return @a RESULT_OK on success, or an error code. */ virtual result_t addFromFile(const string& filename, unsigned int lineNo, vector* row, string* errorDescription, bool replace) = 0; /** * Left and right trim the string. * @param str the @a string to trim. */ static void trim(string* str); /** * Convert all upper case characters in the string to lower case. * @param str the @a string to convert. */ static void tolower(string* str); /** * Check the input string against the search pattern. * @param input the input string to check. * @param search the search pattern to match against. May contain alternatives separated by a "|". * Each alternative may * - start with "^" to match the beginning of the input, * - end with "$" to match the end of the input, * - contain a single "*" (between other characters) to match an arbitrary number of characters. * @param ignoreCase true to ignore case differences. * @param searchIsLower true if search is already known to be in lowercase only. * @return true if the input string matches the search pattern. */ static bool matches(const string& input, const string& search, bool ignoreCase = false, bool searchIsLower = false); /** * Split the next line(s) from the @a istream into fields. * @param stream the @a istream to read from. * @param row the @a vector to which to add the fields. This will be empty for completely empty and comment lines. * @param lineNo the current line number (incremented with each line read). * @param hash optional pointer to a @a size_t value for combining the hash of the line with, or nullptr. * @param size optional pointer to a @a size_t value to add the trimmed line length to, or nullptr. * @return true if there are more lines to read, false when there are no more lines left. */ static bool splitFields(istream* stream, vector* row, unsigned int* lineNo, size_t* hash = nullptr, size_t* size = nullptr); /** * Format the specified hash as 8 hex digits to the output stream. * @param hash the hash code. * @param stream the @a ostream to write to. */ static void formatHash(size_t hash, ostream* stream) { *stream << std::hex << std::setw(8) << std::setfill('0') << (hash & 0xffffffff) << std::dec << std::setw(0); } /** * Format the error description with the input data. * @param filename the name of the file. * @param lineNo the line number in the file. * @param result the result code. * @param error the error message. * @param errorDescription a string in which to store the error description. * @return the result code. */ static result_t formatError(const string& filename, unsigned int lineNo, result_t result, const string& error, string* errorDescription); }; /** * An abstract class derived from @a FileReader that additionally allows to using mapped name/value pairs with one * main map and many sub maps. */ class MappedFileReader : public FileReader { public: /** * Constructor. * @param supportsDefaults whether this instance supports rows with defaults (starting with a star). * @param preferLanguage the preferred language code, or empty. */ explicit MappedFileReader(bool supportsDefaults, const string& preferLanguage = "") : FileReader(), m_supportsDefaults(supportsDefaults), m_preferLanguage(normalizeLanguage(preferLanguage)) { } /** * Destructor. */ virtual ~MappedFileReader() { m_columnNames.clear(); m_lastDefaults.clear(); m_lastSubDefaults.clear(); } /** * Normalize the language string to a lower case, max. 2 characters long language code. * @param lang the language string to normalize. * @return the normalized language code. */ static const string normalizeLanguage(const string& lang); // @copydoc result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose, map* defaults, string* errorDescription, bool replace = false, size_t* hash = nullptr, size_t* size = nullptr) override; /** * Extract default values from the file name. * @param filename the name of the file (without path) * @param defaults the default values by name to add to. * @param destAddress optional pointer to a variable in which to store the numeric destination address, or nullptr. * @param software optional pointer to a in which to store the numeric software version, or nullptr. * @param hardware optional pointer to a in which to store the numeric hardware version, or nullptr. * @return true if the minimum parts were extracted, false otherwise. */ virtual bool extractDefaultsFromFilename(const string& filename, map* defaults, symbol_t* destAddress = nullptr, unsigned int* software = nullptr, unsigned int* hardware = nullptr) const { return false; } // @copydoc result_t addFromFile(const string& filename, unsigned int lineNo, vector* row, string* errorDescription, bool replace) override; /** * Get the field mapping from the given first line. * @param row the first line from which to extract the field mapping, or empty to use the default mapping. * Columns set to @a SKIP_COLUMN are skipped in @a addFromFile(). Columns starting with a "*" mark the beginning of * a repeated sub row. * @param errorDescription a string in which to store the error description in case of error. * @param preferLanguage the preferred language code (up to 2 characters), or empty. * @return @a RESULT_OK on success, or an error code. */ virtual result_t getFieldMap(const string& preferLanguage, vector* row, string* errorDescription) const = 0; /** * Add a default row that was read from a file. * @param row the default row by field name. * @param subRows the sub default rows, each by field name. * @param errorDescription a string in which to store the error description in case of error. * @param filename the name of the file being read. * @param lineNo the current line number in the file being read. * @return @a RESULT_OK on success, or an error code. */ virtual result_t addDefaultFromFile(const string& filename, unsigned int lineNo, map* row, vector< map >* subRows, string* errorDescription) { *errorDescription = "defaults not supported"; return RESULT_ERR_INVALID_ARG; } /** * Add a definition that was read from a file. * @param filename the name of the file being read. * @param lineNo the current line number in the file being read. * @param row the main definition row by field name (may be modified). * @param subRows the sub definition rows, each by field name (may be modified). * @param errorDescription a string in which to store the error description in case of error. * @param replace whether to replace an already existing entry. * @return @a RESULT_OK on success, or an error code. */ virtual result_t addFromFile(const string& filename, unsigned int lineNo, map* row, vector< map >* subRows, string* errorDescription, bool replace = false) = 0; /** * @return a reference to all previously extracted default values by type and field name. */ map >& getDefaults() { return m_lastDefaults; } /** * @return a reference to all previously extracted sub default values by type and field name. */ map > >& getSubDefaults() { return m_lastSubDefaults; } /** * Combine the row to a single string. * @param row the mapped row. * @return the combined string. */ static const string combineRow(const map& row); protected: /** a @a Mutex for access to defaults. */ Mutex m_mutex; private: /** whether this instance supports rows with defaults (starting with a star). */ const bool m_supportsDefaults; /** the preferred language code (up to 2 characters), or empty. */ const string m_preferLanguage; /** the name of each column. */ vector m_columnNames; /** all previously extracted default values by type and field name. */ map > m_lastDefaults; /** all previously extracted sub default values by type and field name. */ map > > m_lastSubDefaults; }; } // namespace ebusd #endif // LIB_EBUS_FILEREADER_H_