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