1 /* 2 * ebusd - daemon for communication with eBUS heating systems. 3 * Copyright (C) 2014-2021 John Baier <ebusd@ebusd.eu> 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #ifndef LIB_EBUS_FILEREADER_H_ 20 #define LIB_EBUS_FILEREADER_H_ 21 22 #include <algorithm> 23 #include <map> 24 #include <string> 25 #include <vector> 26 #include <iomanip> 27 #include "lib/ebus/symbol.h" 28 #include "lib/ebus/result.h" 29 #include "lib/utils/thread.h" 30 31 namespace ebusd { 32 33 /** @file lib/ebus/filereader.h 34 * Helper class and constants for reading configuration files. 35 * 36 * The @a FileReader template class allows to read CSV compliant text files 37 * while splitting each read line into fields. 38 * It also supports special treatment of comment lines starting with a "#", as 39 * well as so called "default values" indicated by the first field starting 40 * with a "*" symbol. 41 */ 42 43 using std::string; 44 using std::map; 45 using std::ostream; 46 using std::istream; 47 48 /** the separator character used between fields. */ 49 #define FIELD_SEPARATOR ',' 50 51 /** the separator character used to quote text having the @a FIELD_SEPARATOR in it. */ 52 #define TEXT_SEPARATOR '"' 53 54 /** the separator character as string used to quote text having the @a FIELD_SEPARATOR in it. */ 55 #define TEXT_SEPARATOR_STR "\"" 56 57 /** the separator character used between multiple values (in CSV only). */ 58 #define VALUE_SEPARATOR ';' 59 60 /** special marker string for skipping columns in @a MappedFileReader. */ 61 static const char SKIP_COLUMN[] = "\b"; 62 63 /** 64 * An abstract class that support reading definitions from a file. 65 */ 66 class FileReader { 67 public: 68 /** 69 * Constructor. 70 */ FileReader()71 FileReader() {} 72 73 /** 74 * Destructor. 75 */ ~FileReader()76 virtual ~FileReader() {} 77 78 /** 79 * Open a file as stream for reading. 80 * @param filename the name of the file being read. 81 * @param errorDescription a string in which to store the error description in case of error. 82 * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. 83 * @return the opened @a istream on success, or nullptr on error. 84 */ 85 static istream* openFile(const string& filename, string* errorDescription, time_t* time = nullptr); 86 87 /** 88 * Read the definitions from a stream. 89 * @param stream the @a istream to read from. 90 * @param filename the relative name of the file being read. 91 * @param mtime a @a time_t value with the modification time of the file. 92 * @param verbose whether to verbosely log problems. 93 * @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults. 94 * @param errorDescription a string in which to store the error description in case of error. 95 * @param replace whether to replace an already existing entry. 96 * @param hash optional pointer to a @a size_t value for storing the hash of the file, or nullptr. 97 * @param size optional pointer to a @a size_t value for storing the normalized size of the file, or nullptr. 98 * @return @a RESULT_OK on success, or an error code. 99 */ 100 virtual result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose, 101 map<string, string>* defaults, string* errorDescription, bool replace = false, size_t* hash = nullptr, 102 size_t* size = nullptr); 103 104 /** 105 * Read a single line definition from the stream. 106 * @param stream the @a istream to read from. 107 * @param filename the name of the file being read. 108 * @param verbose whether to verbosely log problems. 109 * @param lineNo the last line number (incremented with each line read). 110 * @param row the definition row to clear and update with the read data (for performance reasons only). 111 * @param errorDescription a string in which to store the error description in case of error. 112 * @param replace whether to replace an already existing entry. 113 * @param hash optional pointer to a @a size_t value for updating with the hash of the line, or nullptr. 114 * @param size optional pointer to a @a size_t value for updating with the normalized length of the line, or nullptr. 115 * @return @a RESULT_OK on success, or an error code. 116 */ 117 virtual result_t readLineFromStream(istream* stream, const string& filename, bool verbose, 118 unsigned int* lineNo, vector<string>* row, string* errorDescription, bool replace, size_t* hash, size_t* size); 119 120 /** 121 * Add a definition that was read from a file. 122 * @param filename the name of the file being read. 123 * @param lineNo the current line number in the file being read. 124 * @param row the definition row (allowed to be modified). 125 * @param errorDescription a string in which to store the error description in case of error. 126 * @param replace whether to replace an already existing entry. 127 * @return @a RESULT_OK on success, or an error code. 128 */ 129 virtual result_t addFromFile(const string& filename, unsigned int lineNo, vector<string>* row, 130 string* errorDescription, bool replace) = 0; 131 132 /** 133 * Left and right trim the string. 134 * @param str the @a string to trim. 135 */ 136 static void trim(string* str); 137 138 /** 139 * Convert all upper case characters in the string to lower case. 140 * @param str the @a string to convert. 141 */ 142 static void tolower(string* str); 143 144 /** 145 * Split the next line(s) from the @a istream into fields. 146 * @param stream the @a istream to read from. 147 * @param row the @a vector to which to add the fields. This will be empty for completely empty and comment lines. 148 * @param lineNo the current line number (incremented with each line read). 149 * @param hash optional pointer to a @a size_t value for combining the hash of the line with, or nullptr. 150 * @param size optional pointer to a @a size_t value to add the trimmed line length to, or nullptr. 151 * @return true if there are more lines to read, false when there are no more lines left. 152 */ 153 static bool splitFields(istream* stream, vector<string>* row, unsigned int* lineNo, 154 size_t* hash = nullptr, size_t* size = nullptr); 155 156 /** 157 * Format the specified hash as 8 hex digits to the output stream. 158 * @param hash the hash code. 159 * @param stream the @a ostream to write to. 160 */ formatHash(size_t hash,ostream * stream)161 static void formatHash(size_t hash, ostream* stream) { 162 *stream << std::hex << std::setw(8) << std::setfill('0') << (hash & 0xffffffff) << std::dec << std::setw(0); 163 } 164 165 /** 166 * Format the error description with the input data. 167 * @param filename the name of the file. 168 * @param lineNo the line number in the file. 169 * @param result the result code. 170 * @param error the error message. 171 * @param errorDescription a string in which to store the error description. 172 * @return the result code. 173 */ 174 static result_t formatError(const string& filename, unsigned int lineNo, result_t result, 175 const string& error, string* errorDescription); 176 }; 177 178 179 /** 180 * An abstract class derived from @a FileReader that additionally allows to using mapped name/value pairs with one 181 * main map and many sub maps. 182 */ 183 class MappedFileReader : public FileReader { 184 public: 185 /** 186 * Constructor. 187 * @param supportsDefaults whether this instance supports rows with defaults (starting with a star). 188 * @param preferLanguage the preferred language code, or empty. 189 */ 190 explicit MappedFileReader(bool supportsDefaults, const string& preferLanguage = "") FileReader()191 : FileReader(), m_supportsDefaults(supportsDefaults), m_preferLanguage(normalizeLanguage(preferLanguage)) { 192 } 193 194 /** 195 * Destructor. 196 */ ~MappedFileReader()197 virtual ~MappedFileReader() { 198 m_columnNames.clear(); 199 m_lastDefaults.clear(); 200 m_lastSubDefaults.clear(); 201 } 202 203 /** 204 * Normalize the language string to a lower case, max. 2 characters long language code. 205 * @param lang the language string to normalize. 206 * @return the normalized language code. 207 */ 208 static const string normalizeLanguage(const string& lang); 209 210 // @copydoc 211 result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose, 212 map<string, string>* defaults, string* errorDescription, bool replace = false, size_t* hash = nullptr, 213 size_t* size = nullptr) override; 214 215 /** 216 * Extract default values from the file name. 217 * @param filename the name of the file (without path) 218 * @param defaults the default values by name to add to. 219 * @param destAddress optional pointer to a variable in which to store the numeric destination address, or nullptr. 220 * @param software optional pointer to a in which to store the numeric software version, or nullptr. 221 * @param hardware optional pointer to a in which to store the numeric hardware version, or nullptr. 222 * @return true if the minimum parts were extracted, false otherwise. 223 */ 224 virtual bool extractDefaultsFromFilename(const string& filename, map<string, string>* defaults, 225 symbol_t* destAddress = nullptr, unsigned int* software = nullptr, unsigned int* hardware = nullptr) const { 226 return false; 227 } 228 229 // @copydoc 230 result_t addFromFile(const string& filename, unsigned int lineNo, vector<string>* row, 231 string* errorDescription, bool replace) override; 232 233 /** 234 * Get the field mapping from the given first line. 235 * @param row the first line from which to extract the field mapping, or empty to use the default mapping. 236 * Columns set to @a SKIP_COLUMN are skipped in @a addFromFile(). Columns starting with a "*" mark the beginning of 237 * a repeated sub row. 238 * @param errorDescription a string in which to store the error description in case of error. 239 * @param preferLanguage the preferred language code (up to 2 characters), or empty. 240 * @return @a RESULT_OK on success, or an error code. 241 */ 242 virtual result_t getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const = 0; 243 244 /** 245 * Add a default row that was read from a file. 246 * @param row the default row by field name. 247 * @param subRows the sub default rows, each by field name. 248 * @param errorDescription a string in which to store the error description in case of error. 249 * @param filename the name of the file being read. 250 * @param lineNo the current line number in the file being read. 251 * @return @a RESULT_OK on success, or an error code. 252 */ addDefaultFromFile(const string & filename,unsigned int lineNo,map<string,string> * row,vector<map<string,string>> * subRows,string * errorDescription)253 virtual result_t addDefaultFromFile(const string& filename, unsigned int lineNo, map<string, string>* row, 254 vector< map<string, string> >* subRows, string* errorDescription) { 255 *errorDescription = "defaults not supported"; 256 return RESULT_ERR_INVALID_ARG; 257 } 258 259 /** 260 * Add a definition that was read from a file. 261 * @param filename the name of the file being read. 262 * @param lineNo the current line number in the file being read. 263 * @param row the main definition row by field name (may be modified). 264 * @param subRows the sub definition rows, each by field name (may be modified). 265 * @param errorDescription a string in which to store the error description in case of error. 266 * @param replace whether to replace an already existing entry. 267 * @return @a RESULT_OK on success, or an error code. 268 */ 269 virtual result_t addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row, 270 vector< map<string, string> >* subRows, string* errorDescription, bool replace = false) = 0; 271 272 /** 273 * @return a reference to all previously extracted default values by type and field name. 274 */ getDefaults()275 map<string, map<string, string> >& getDefaults() { 276 return m_lastDefaults; 277 } 278 279 /** 280 * @return a reference to all previously extracted sub default values by type and field name. 281 */ getSubDefaults()282 map<string, vector< map<string, string> > >& getSubDefaults() { 283 return m_lastSubDefaults; 284 } 285 286 /** 287 * Combine the row to a single string. 288 * @param row the mapped row. 289 * @return the combined string. 290 */ 291 static const string combineRow(const map<string, string>& row); 292 293 protected: 294 /** a @a Mutex for access to defaults. */ 295 Mutex m_mutex; 296 297 private: 298 /** whether this instance supports rows with defaults (starting with a star). */ 299 const bool m_supportsDefaults; 300 301 /** the preferred language code (up to 2 characters), or empty. */ 302 const string m_preferLanguage; 303 304 /** the name of each column. */ 305 vector<string> m_columnNames; 306 307 /** all previously extracted default values by type and field name. */ 308 map<string, map<string, string> > m_lastDefaults; 309 310 /** all previously extracted sub default values by type and field name. */ 311 map<string, vector< map<string, string> > > m_lastSubDefaults; 312 }; 313 314 } // namespace ebusd 315 316 #endif // LIB_EBUS_FILEREADER_H_ 317