diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 56faba15..aa3dc83f --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,6 @@ set(PACKAGE_BUGREPORT "ebusd@ebusd.eu") set(PACKAGE_URL "https://github.com/john30/ebusd") set(PACKAGE_PIDFILE "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/run/${PACKAGE}.pid") set(PACKAGE_LOGFILE "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/${PACKAGE}.log") -set(PACKAGE_CONFIGPATH "${CMAKE_INSTALL_FULL_SYSCONFDIR}/${PACKAGE}") execute_process(COMMAND echo -n ${VERSION} COMMAND sed "-e" "s/^\\([0-9]*\\.[0-9]*\\).*/\\1/" "-e" "s/\\.\\([0-9]\\)\$/0\\1/" "-e" "s/\\.//" OUTPUT_VARIABLE SCAN_VERSION) diff --git a/config.h.cmake b/config.h.cmake old mode 100644 new mode 100755 index e24ef367..5e88633c --- a/config.h.cmake +++ b/config.h.cmake @@ -25,9 +25,6 @@ /* The address where bug reports for this package should be sent. */ #cmakedefine PACKAGE_BUGREPORT "${PACKAGE_BUGREPORT}" -/* The default path of the configuration files. */ -#define PACKAGE_CONFIGPATH SYSCONFDIR "/" PACKAGE - /* The path and name of the log file. */ #define PACKAGE_LOGFILE LOCALSTATEDIR "/log/" PACKAGE ".log" diff --git a/configure.ac b/configure.ac old mode 100644 new mode 100755 index 0e78d23f..08bed21d --- a/configure.ac +++ b/configure.ac @@ -119,7 +119,6 @@ AM_COND_IF([CONTRIB], [AC_CONFIG_FILES([ AC_DEFINE_UNQUOTED(PACKAGE_PIDFILE, LOCALSTATEDIR "/run/" PACKAGE ".pid", [The path and name of the PID file.]) AC_DEFINE_UNQUOTED(PACKAGE_LOGFILE, LOCALSTATEDIR "/log/" PACKAGE ".log", [The path and name of the log file.]) -AC_DEFINE_UNQUOTED(PACKAGE_CONFIGPATH, SYSCONFDIR "/" PACKAGE, [The default path of the configuration files.]) AC_DEFINE(SCAN_VERSION, "[m4_esyscmd_s([sed -e 's#^\([0-9]*\.[0-9]*\).*#\1#' -e 's#\.\([0-9]\)$#0\1#' -e 's#\.##' VERSION])]", [The version of the package formatted for the scan result.]) AC_DEFINE(REVISION, "[m4_esyscmd_s([git describe --always 2>/dev/null || (date +p%Y%m%d)])]", [The revision of the package.]) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 4c6e2d52..59bca70a 100755 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -32,6 +32,7 @@ #include #include "ebusd/mainloop.h" #include "lib/utils/log.h" +#include "lib/utils/httpclient.h" /** the version string of the program. */ @@ -64,11 +65,7 @@ using std::cout; #endif /** the default path of the configuration files. */ -#ifdef PACKAGE_CONFIGPATH -#define CONFIG_PATH PACKAGE_CONFIGPATH -#else -#define CONFIG_PATH "/etc/ebusd" -#endif +#define CONFIG_PATH "http://ebusd.eu/config/" /** the opened PID file, or NULL. */ static FILE* pidFile = NULL; @@ -133,6 +130,15 @@ static MessageMap* s_messageMap = NULL; /** the @a MainLoop instance, or NULL. */ static MainLoop* s_mainLoop = NULL; +/** the path prefix (including trailing "/") for retrieving configuration files from local file system (empty for HTTP). */ +static string s_configLocalPrefix; + +/** the URI prefix (including trailing "/") for retrieving configuration files from HTTP (empty for local files). */ +static string s_configUriPrefix; + +/** the @a HttpClient for retrieving configuration files from HTTP. */ +static HttpClient s_configHttpClient; + /** the documentation of the program. */ static const char argpdoc[] = "A daemon for communication with eBUS heating systems."; @@ -239,7 +245,7 @@ static const struct argp_option argpoptions[] = { static DataFieldTemplates s_globalTemplates; /** - * the loaded @a DataFieldTemplates by path (may also carry + * the loaded @a DataFieldTemplates by relative path (may also carry * @a globalTemplates as replacement for missing file). */ static map s_templatesByPath; @@ -717,37 +723,62 @@ void signalHandler(int sig) { /** * Collect configuration files matching the prefix and extension from the specified path. - * @param path the path from which to collect the files. + * @param relPath the relative path from which to collect the files (without trailing "/"). * @param prefix the filename prefix the files have to match, or empty. * @param extension the filename extension the files have to match. * @param files the @a vector to which to add the matching files. + * @param query the query string suffix for HTTP retrieval starting with "&", or empty. * @param dirs the @a vector to which to add found directories (without any name check), or NULL to ignore. * @param hasTemplates the bool to set when the templates file was found in the path, or NULL to ignore. * @return the result code. */ -static result_t collectConfigFiles(const string path, const string prefix, const string extension, - vector* files, vector* dirs = NULL, bool* hasTemplates = NULL) { +static result_t collectConfigFiles(const string& relPath, const string& prefix, const string& extension, + vector* files, const bool ignoreAddressPrefix = false, const string& query = "", vector* dirs = NULL, + bool* hasTemplates = NULL) { + const string relPathWithSlash = relPath.empty() ? "" : relPath + "/"; + if (!s_configUriPrefix.empty()) { + string names; + if (!s_configHttpClient.get(s_configUriPrefix + relPathWithSlash + "?t=" + (extension.substr(1)) + query, "", names)) { + return RESULT_ERR_NOTFOUND; + } + istringstream stream(names); + string name; + while (getline(stream, name)) { + if (name.empty()) { + continue; + } + if (name == "_templates"+extension) { + if (hasTemplates) { + *hasTemplates = true; + } + continue; + } + if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) + : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { + files->push_back(relPathWithSlash + name); + } + } + return RESULT_OK; + } + const string path = s_configLocalPrefix + relPathWithSlash; DIR* dir = opendir(path.c_str()); - if (dir == NULL) { return RESULT_ERR_NOTFOUND; } dirent* d; while ((d = readdir(dir)) != NULL) { string name = d->d_name; - if (name == "." || name == "..") { continue; } - const string p = path + "/" + name; + const string p = path + name; struct stat stat_buf; - if (stat(p.c_str(), &stat_buf) != 0) { continue; } if (S_ISDIR(stat_buf.st_mode)) { if (dirs != NULL) { - dirs->push_back(p); + dirs->push_back(relPathWithSlash + name); } } else if (S_ISREG(stat_buf.st_mode) && name.length() >= extension.length() && name.substr(name.length()-extension.length()) == extension) { @@ -755,9 +786,11 @@ static result_t collectConfigFiles(const string path, const string prefix, const if (hasTemplates) { *hasTemplates = true; } - } else if (prefix.length() == 0 - || (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { - files->push_back(p); + continue; + } + if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) + : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { + files->push_back(relPathWithSlash + name); } } } @@ -781,63 +814,65 @@ DataFieldTemplates* getTemplates(const string& filename) { /** * Read the @a DataFieldTemplates for the specified path if necessary. - * @param path the path from which to read the files. + * @param relPath the relative path from which to read the files (without trailing "/"). * @param extension the filename extension of the files to read. * @param available whether the templates file is available in the path. * @param verbose whether to verbosely log problems. * @return false when the templates for the path were already loaded before, true when the templates for the path were added (independent from @a available). * @return the @a DataFieldTemplates. */ -static bool readTemplates(const string path, const string extension, bool available, bool verbose = false) { - const auto it = s_templatesByPath.find(path); +static bool readTemplates(const string relPath, const string extension, bool available, bool verbose = false) { + const auto it = s_templatesByPath.find(relPath); if (it != s_templatesByPath.end()) { return false; } DataFieldTemplates* templates; - if (path == opt.configPath || !available) { + if (relPath.empty() || !available) { templates = &s_globalTemplates; } else { templates = new DataFieldTemplates(s_globalTemplates); } - s_templatesByPath[path] = templates; + s_templatesByPath[relPath] = templates; if (!available) { // global templates are stored as replacement in order to determine whether the directory was already loaded return true; } string errorDescription; - logInfo(lf_main, "reading templates %s", path.c_str()); - result_t result = templates->readFromFile(path+"/_templates"+extension, verbose, NULL, &errorDescription, - NULL, NULL, NULL); + string logPath = relPath.empty() ? "/" : relPath; + logInfo(lf_main, "reading templates %s", logPath.c_str()); + string file = (relPath.empty() ? "" : relPath + "/") + "_templates" + extension; + result_t result = loadDefinitionsFromConfigPath(templates, file, verbose, NULL, &errorDescription); if (result == RESULT_OK) { - logInfo(lf_main, "read templates in %s", path.c_str()); + logInfo(lf_main, "read templates in %s", logPath.c_str()); return true; } - logError(lf_main, "error reading templates in %s: %s, last error: %s", path.c_str(), getResultCode(result), - errorDescription.c_str()); + logError(lf_main, "error reading templates in %s: %s, last error: %s", logPath.c_str(), getResultCode(result), + errorDescription.c_str()); return false; } /** * Read the configuration files from the specified path. - * @param path the path from which to read the files. + * @param relPath the relative path from which to read the files (without trailing "/"). * @param extension the filename extension of the files to read. * @param messages the @a MessageMap to load the messages into. * @param recursive whether to load all files recursively. * @param verbose whether to verbosely log problems. + * @param errorDescription a string in which to store the error description in case of error. * @return the result code. */ -static result_t readConfigFiles(const string& path, const string& extension, const bool recursive, +static result_t readConfigFiles(const string& relPath, const string& extension, const bool recursive, const bool verbose, string* errorDescription, MessageMap* messages) { vector files, dirs; bool hasTemplates = false; - result_t result = collectConfigFiles(path, "", extension, &files, &dirs, &hasTemplates); + result_t result = collectConfigFiles(relPath, "", extension, &files, false, "", &dirs, &hasTemplates); if (result != RESULT_OK) { return result; } - readTemplates(path, extension, hasTemplates, verbose); + readTemplates(relPath, extension, hasTemplates, verbose); for (const auto& name : files) { logInfo(lf_main, "reading file %s", name.c_str()); - result = messages->readFromFile(name, verbose, NULL, errorDescription, NULL, NULL, NULL); + result_t result = loadDefinitionsFromConfigPath(messages, name, verbose, NULL, errorDescription); if (result != RESULT_OK) { return result; } @@ -891,6 +926,28 @@ void executeInstructions(MessageMap* messages, bool verbose) { messages->sizeConditional(), messages->sizeConditions(), messages->sizePoll(), messages->sizePassive()); } +result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, + map* defaults, string* errorDescription) { + istream* stream = NULL; + time_t mtime; + if (s_configUriPrefix.empty()) { + stream = FileReader::openFile(s_configLocalPrefix + filename, errorDescription, &mtime); + } else { + string content; + if (s_configHttpClient.get(s_configUriPrefix + filename, "", content, &mtime)) { + stream = new istringstream(content); + } + } + result_t result; + if (stream) { + result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription); + delete(stream); + } else { + result = RESULT_ERR_NOTFOUND; + } + return result; +} + result_t loadConfigFiles(MessageMap* messages, bool verbose, bool denyRecursive) { logInfo(lf_main, "loading configuration files from %s", opt.configPath); messages->lock(); @@ -905,7 +962,7 @@ result_t loadConfigFiles(MessageMap* messages, bool verbose, bool denyRecursive) s_templatesByPath.clear(); string errorDescription; - result_t result = readConfigFiles(string(opt.configPath), ".csv", + result_t result = readConfigFiles("", ".csv", (!opt.scanConfig || opt.checkConfig) && !denyRecursive, verbose, &errorDescription, messages); if (result == RESULT_OK) { logInfo(lf_main, "read config files"); @@ -928,22 +985,22 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose return RESULT_EMPTY; } DataFieldSet* identFields = DataFieldSet::getIdentFields(); - string path, prefix, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx + string manufStr, addrStr, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx unsigned int sw = 0, hw = 0; ostringstream out; size_t offset = 0; size_t field = 0; + bool fromLocal = s_configUriPrefix.empty(); result_t result = (*identFields)[field]->read(data, offset, false, NULL, -1, 0, -1, &out); // manufacturer name - if (result == RESULT_ERR_NOTFOUND) { + if (result == RESULT_ERR_NOTFOUND && fromLocal) { result = (*identFields)[field]->read(data, offset, false, NULL, -1, OF_NUMERIC, -1, &out); // manufacturer name } if (result == RESULT_OK) { - path = out.str(); - transform(path.begin(), path.end(), path.begin(), ::tolower); - path = string(opt.configPath) + "/" + path; + manufStr = out.str(); + transform(manufStr.begin(), manufStr.end(), manufStr.begin(), ::tolower); out.str(""); - out << setw(2) << hex << setfill('0') << nouppercase << static_cast(address) << "."; - prefix = out.str(); + out << setw(2) << hex << setfill('0') << nouppercase << static_cast(address); + addrStr = out.str(); out.str(""); out.clear(); offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); @@ -952,6 +1009,7 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose if (result == RESULT_OK) { ident = out.str(); out.str(""); + out.clear(); offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); result = (*identFields)[field]->read(data, offset, NULL, -1, &sw); // software version number if (result == RESULT_ERR_OUT_OF_RANGE) { @@ -972,22 +1030,10 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose identFields->getName(field).c_str(), getResultCode(result)); return result; } - vector files; bool hasTemplates = false; - // find files matching MANUFACTURER/ZZ.*csv in cfgpath - result = collectConfigFiles(path, prefix, ".csv", &files, NULL, &hasTemplates); - if (result != RESULT_OK) { - logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, path.c_str(), - getResultCode(result)); - return result; - } - if (files.empty()) { - logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address, path.c_str(), - prefix.c_str()); - return RESULT_ERR_NOTFOUND; - } - logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), path.c_str(), - prefix.c_str(), getResultCode(result)); + string best; + map bestDefaults; + vector files; auto it = ident.begin(); while (it != ident.end()) { if (*it != '_' && !::isalnum(*it)) { @@ -997,15 +1043,34 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose it++; } } + // find files matching MANUFACTURER/ZZ.*csv in cfgpath + string query; + if (!fromLocal) { + out << "&a=" << addrStr << "&i=" << ident << "&h=" << dec << static_cast(hw) << "&s=" << dec << static_cast(sw);; + query = out.str(); + out.str(""); + out.clear(); + } + result = collectConfigFiles(manufStr, addrStr + ".", ".csv", &files, false, query, NULL, &hasTemplates); + if (result != RESULT_OK) { + logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, manufStr.c_str(), + getResultCode(result)); + return result; + } + if (files.empty()) { + logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address, manufStr.c_str(), + addrStr.c_str()); + return RESULT_ERR_NOTFOUND; + } + logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), manufStr.c_str(), + addrStr.c_str(), getResultCode(result)); // complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.circuit][.suffix][.*][.SWxxxx][.HWxxxx][.*].csv size_t bestMatch = 0; - string best; - map bestDefaults; for (const auto& name : files) { symbol_t checkDest; unsigned int checkSw, checkHw; map defaults; - const string filename = name.substr(path.length()+1); + const string filename = name.substr(manufStr.length()+1); if (!messages->extractDefaultsFromFilename(filename, &defaults, &checkDest, &checkSw, &checkHw)) { continue; } @@ -1042,23 +1107,23 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose if (best.empty()) { logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s matches ID \"%s\", SW%4.4d, HW%4.4d", - address, path.c_str(), prefix.c_str(), ident.c_str(), sw, hw); + address, manufStr.c_str(), addrStr.c_str(), ident.c_str(), sw, hw); return RESULT_ERR_NOTFOUND; } // found the right file. load the templates if necessary, then load the file itself - bool readCommon = readTemplates(path, ".csv", hasTemplates, opt.checkConfig); + bool readCommon = readTemplates(manufStr, ".csv", hasTemplates, opt.checkConfig); if (readCommon) { - result = collectConfigFiles(path, "", ".csv", &files); + result = collectConfigFiles(manufStr, "", ".csv", &files, true, "&a=-"); if (result == RESULT_OK && !files.empty()) { for (const auto& name : files) { - string baseName = name.substr(path.length()+1, name.length()-path.length()-strlen(".csv")); // *. + string baseName = name.substr(manufStr.length()+1, name.length()-manufStr.length()-strlen(".csv")); // *. if (baseName == "_templates.") { // skip templates continue; } if (baseName.length() < 3 || baseName.find_first_of('.') != 2) { // different from the scheme "ZZ." string errorDescription; - result = messages->readFromFile(name, verbose, NULL, &errorDescription, NULL, NULL, NULL); + result = loadDefinitionsFromConfigPath(messages, name, verbose, NULL, &errorDescription); if (result == RESULT_OK) { logNotice(lf_main, "read common config file %s", name.c_str()); } else { @@ -1069,16 +1134,16 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose } } } - string errorDescription; bestDefaults["name"] = ident; - result = messages->readFromFile(best, verbose, &bestDefaults, &errorDescription, NULL, NULL, NULL); + string errorDescription; + result = loadDefinitionsFromConfigPath(messages, best, verbose, &bestDefaults, &errorDescription); if (result != RESULT_OK) { logError(lf_main, "error reading scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d: %s, %s", best.c_str(), ident.c_str(), sw, hw, getResultCode(result), errorDescription.c_str()); return result; } logNotice(lf_main, "read scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d", best.c_str(), ident.c_str(), sw, hw); - *relativeFile = best.substr(strlen(opt.configPath)+1); + *relativeFile = best; return RESULT_OK; } @@ -1135,6 +1200,26 @@ int main(int argc, char* argv[]) { return EINVAL; } + string configPath = string(opt.configPath); + if (configPath.find("://") == string::npos) { + s_configLocalPrefix = configPath[configPath.length()-1] == '/' ? configPath : configPath + "/"; + } else { + if (!opt.scanConfig) { + logError(lf_main, "invalid configpath without scanconfig"); + return EINVAL; + } + uint16_t configPort = 80; + string proto, configHost; + if (!HttpClient::parseUrl(configPath, proto, configHost, configPort, s_configUriPrefix)) { + logError(lf_main, "invalid configPath URL"); + return EINVAL; + } + if (!s_configHttpClient.connect(configHost, configPort, PACKAGE_NAME "/" PACKAGE_VERSION)) { + logError(lf_main, "invalid configPath URL"); + return EINVAL; + } + s_configHttpClient.disconnect(); + } if (!opt.readOnly && opt.scanConfig && opt.initialScan == 0) { opt.initialScan = BROADCAST; } @@ -1143,7 +1228,7 @@ int main(int argc, char* argv[]) { setFacilitiesLogLevel(opt.logAreas, opt.logLevel); } - s_messageMap = new MessageMap(string(opt.configPath)+"/", opt.checkConfig); + s_messageMap = new MessageMap(opt.checkConfig); if (opt.checkConfig) { logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check..."); @@ -1178,7 +1263,6 @@ int main(int argc, char* argv[]) { return 0; } - // open the device Device *device = Device::create(opt.device, !opt.noDeviceCheck, opt.readOnly, opt.initialSend); if (device == NULL) { diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 4a3e94a9..873b1aba 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -120,6 +120,18 @@ result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose */ void executeInstructions(MessageMap* messages, bool verbose = false); +/** + * Helper method for loading definitions from a relative file from the config path/URL. + * @param reader the @a FileReader instance to load with the definitions. + * @param filename the relative name of the file being read. + * @param verbose whether to verbosely log problems. + * @param defaults the default values by name (potentially overwritten by file name), or NULL to not use defaults. + * @param errorDescription a string in which to store the error description in case of error. + * @return @a RESULT_OK on success, or an error code. + */ +result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, + map* defaults, string* errorDescription); + } // namespace ebusd #endif // EBUSD_MAIN_H_ diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c1684208..cd7f2250 100755 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -128,7 +128,14 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag m_logRawLastSymbol = SYN; if (opt.aclFile[0]) { string errorDescription; - result = m_userList.readFromFile(opt.aclFile, false, NULL, &errorDescription, NULL, NULL, NULL); + time_t mtime; + istream* stream = FileReader::openFile(opt.aclFile, &errorDescription, &mtime); + if (stream) { + result = m_userList.readFromStream(stream, opt.aclFile, mtime, false, NULL, &errorDescription); + delete(stream); + } else { + result = RESULT_ERR_NOTFOUND; + } if (result != RESULT_OK) { logError(lf_main, "error reading ACL file \"%s\": %s", opt.aclFile, getResultCode(result)); } diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index 2c281736..3fd5433c 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -36,43 +36,47 @@ using std::setw; using std::dec; -result_t FileReader::readFromFile(const string& filename, bool verbose, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time) { +istream* FileReader::openFile(const string& filename, string* errorDescription, time_t* time) { struct stat st; if (stat(filename.c_str(), &st) != 0) { *errorDescription = filename; - return RESULT_ERR_NOTFOUND; + return NULL; } if (S_ISDIR(st.st_mode)) { *errorDescription = filename+" is a directory"; - return RESULT_ERR_NOTFOUND; + return NULL; } - ifstream stream; - stream.open(filename.c_str(), ifstream::in); - if (!stream.is_open()) { + ifstream* stream = new ifstream(); + stream->open(filename.c_str(), ifstream::in); + if (!stream->is_open()) { *errorDescription = filename; - return RESULT_ERR_NOTFOUND; + delete(stream); + return NULL; } + if (time) { + *time = st.st_mtime; + } + return stream; +} + +result_t FileReader::readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash, size_t* size) { if (hash) { *hash = 0; } if (size) { *size = 0; } - if (time) { - *time = st.st_mtime; - } unsigned int lineNo = 0; vector row; result_t result = RESULT_OK; - while (stream.peek() != EOF && result == RESULT_OK) { - result = readLineFromStream(filename, verbose, &stream, &lineNo, &row, errorDescription, hash, size); + while (stream->peek() != EOF && result == RESULT_OK) { + result = readLineFromStream(stream, filename, verbose, &lineNo, &row, errorDescription, hash, size); } - stream.close(); return result; } -result_t FileReader::readLineFromStream(const string& filename, bool verbose, istream* stream, +result_t FileReader::readLineFromStream(istream* stream, const string& filename, bool verbose, unsigned int* lineNo, vector* row, string* errorDescription, size_t* hash, size_t* size) { result_t result; if (!splitFields(stream, row, lineNo, hash, size)) { @@ -237,8 +241,8 @@ const string MappedFileReader::normalizeLanguage(const string& lang) { return normLang; } -result_t MappedFileReader::readFromFile(const string& filename, bool verbose, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time) { +result_t MappedFileReader::readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash, size_t* size) { m_mutex.lock(); m_columnNames.clear(); m_lastDefaults.clear(); @@ -248,8 +252,8 @@ result_t MappedFileReader::readFromFile(const string& filename, bool verbose, ma } size_t lastSep = filename.find_last_of('/'); string defaultsPart = lastSep == string::npos ? filename : filename.substr(lastSep+1); - extractDefaultsFromFilename(defaultsPart, &m_lastDefaults[""], NULL, NULL, NULL); - result_t result = FileReader::readFromFile(filename, verbose, defaults, errorDescription, hash, size, time); + extractDefaultsFromFilename(defaultsPart, &m_lastDefaults[""]); + result_t result = FileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, hash, size); m_mutex.unlock(); return result; } diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index b78b7825..307dc9b1 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -76,24 +76,34 @@ class FileReader { virtual ~FileReader() {} /** - * Read the definitions from a file. + * 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 NULL. + * @return the opened @a istream on success, or NULL on error. + */ + static istream* openFile(const string& filename, string* errorDescription, time_t* time = NULL); + + /** + * 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 NULL to not use defaults. * @param errorDescription a string in which to store the error description in case of error. * @param hash optional pointer to a @a size_t value for storing the hash of the file, or NULL. * @param size optional pointer to a @a size_t value for storing the normalized size of the file, or NULL. - * @param time optional pointer to a @a time_t value for storing the modification time of the file, or NULL. * @return @a RESULT_OK on success, or an error code. */ - virtual result_t readFromFile(const string& filename, bool verbose, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time); + virtual result_t readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL); /** * 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 stream the @a istream to read from. * @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. @@ -101,7 +111,7 @@ class FileReader { * @param size optional pointer to a @a size_t value for updating with the normalized length of the line, or NULL. * @return @a RESULT_OK on success, or an error code. */ - virtual result_t readLineFromStream(const string& filename, bool verbose, istream* stream, + virtual result_t readLineFromStream(istream* stream, const string& filename, bool verbose, unsigned int* lineNo, vector* row, string* errorDescription, size_t* hash, size_t* size); /** @@ -194,20 +204,20 @@ class MappedFileReader : public FileReader { static const string normalizeLanguage(const string& lang); // @copydoc - result_t readFromFile(const string& filename, bool verbose, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time) override; + result_t readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL) 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 a pointer to a variable in which to store the numeric destination address, or NULL. - * @param software a pointer to a in which to store the numeric software version, or NULL. - * @param hardware a pointer to a in which to store the numeric hardware version, or NULL. + * @param destAddress optional pointer to a variable in which to store the numeric destination address, or NULL. + * @param software optional pointer to a in which to store the numeric software version, or NULL. + * @param hardware optional pointer to a in which to store the numeric hardware version, or NULL. * @return true if the minimum parts were extracted, false otherwise. */ virtual bool extractDefaultsFromFilename(const string& filename, map* defaults, - symbol_t* destAddress, unsigned int* software, unsigned int* hardware) const { + symbol_t* destAddress = NULL, unsigned int* software = NULL, unsigned int* hardware = NULL) const { return false; } diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 067b1efd..0feb02dc 100755 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -81,6 +81,9 @@ static const char* defaultMessageFieldMap[] = { // access level not included in extern DataFieldTemplates* getTemplates(const string& filename); +extern result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, + map* defaults, string* errorDescription); + Message::Message(const string& circuit, const string& level, const string& name, bool isWrite, bool isPassive, const map& attributes, @@ -1692,10 +1695,9 @@ void Instruction::getDestination(ostringstream* ostream) const { } } - result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) { string errorDescription; - result_t result = messages->readFromFile(m_filename, false, &m_defaults, &errorDescription, NULL, NULL, NULL); + result_t result = loadDefinitionsFromConfigPath(messages, m_filename, false, &m_defaults, &errorDescription); if (log->tellp() > 0) { *log << ", "; } @@ -1731,13 +1733,6 @@ result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) { vector MessageMap::s_noFiles; -const string MessageMap::getRelativePath(const string& filename) const { - if (filename.length() >= m_configPath.length() && filename.substr(0, m_configPath.length()) == m_configPath) { - return filename.substr(m_configPath.length()); - } - return filename; -} - result_t MessageMap::add(bool storeByName, Message* message) { uint64_t key = message->getKey(); bool conditional = message->isConditional(); @@ -2109,20 +2104,16 @@ bool MessageMap::extractDefaultsFromFilename(const string& filename, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time) { +result_t MessageMap::readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash, size_t* size) { size_t localHash, localSize; - time_t localTime; if (!hash) { hash = &localHash; } if (!size) { size = &localSize; } - if (!time) { - time = &localTime; - } - result_t result = MappedFileReader::readFromFile(filename, verbose, defaults, errorDescription, hash, size, time); + result_t result = MappedFileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, hash, size); if (defaults) { string circuit = AttributedItem::pluck("circuit", defaults); if (!circuit.empty() && m_circuitData.find(circuit) == m_circuitData.end()) { @@ -2134,10 +2125,9 @@ result_t MessageMap::readFromFile(const string& filename, bool verbose, map& files = m_loadedFiles[address]; - const string file = getRelativePath(filename); - files.push_back(file); + files.push_back(filename); if (!comment.empty()) { - m_loadedFileInfos[file].m_comment = comment; + m_loadedFileInfos[filename].m_comment = comment; } } } diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 4c1fae9b..2a69c866 100755 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1091,7 +1091,7 @@ class Instruction { /** * Factory method for creating a new instance. - * @param contextPath the path and/or filename context being loaded. + * @param relPath the relative path and/or filename context being loaded. * @param type the type of the instruction. * @param condition the @a Condition for the instruction, or NULL. * @param row the definition row by field name. @@ -1099,7 +1099,7 @@ class Instruction { * @param returnValue the variable in which to store the created instance. * @return @a RESULT_OK on success, or an error code. */ - static result_t create(const string& contextPath, const string& type, + static result_t create(const string& relPath, const string& type, Condition* condition, const map& row, const map& defaults, Instruction** returnValue); @@ -1156,7 +1156,7 @@ class LoadInstruction : public Instruction { * @param singleton whether this @a Instruction belongs to a set of instructions of which only the first one may be * executed for the same source file. * @param defaults the mapped definition defaults. - * @param filename the name of the file to load. + * @param filename the relative name of the file to load. */ LoadInstruction(bool singleton, const map& defaults, const string& filename, Condition* condition) @@ -1172,7 +1172,7 @@ class LoadInstruction : public Instruction { private: - /** the name of the file to load. */ + /** the relative name of the file to load. */ const string m_filename; }; @@ -1207,9 +1207,8 @@ class MessageMap : public MappedFileReader { * @param addAll whether to add all messages, even if duplicate. * @param preferLanguage the preferred language to use, or empty. */ - explicit MessageMap(const string& configPath, bool addAll = false, const string& preferLanguage = "") + explicit MessageMap(bool addAll = false, const string& preferLanguage = "") : MappedFileReader::MappedFileReader(true), - m_configPath(configPath), m_addAll(addAll), m_additionalScanMessages(false), m_maxIdLength(0), m_maxBroadcastIdLength(0), m_messageCount(0), m_conditionalMessageCount(0), m_passiveMessageCount(0) { m_scanMessage = Message::createScanMessage(); @@ -1231,13 +1230,6 @@ class MessageMap : public MappedFileReader { } } - /** - * Return the relative file name of the given filename. - * @param filename the name of the configuration file (including relative path). - * @return the relative file name. - */ - const string getRelativePath(const string& filename) const; - /** * Add a @a Message instance to this set. * @param message the @a Message instance to add. @@ -1266,11 +1258,11 @@ class MessageMap : public MappedFileReader { // @copydoc bool extractDefaultsFromFilename(const string& filename, map* defaults, - symbol_t* destAddress, unsigned int* software, unsigned int* hardware) const override; + symbol_t* destAddress = NULL, unsigned int* software = NULL, unsigned int* hardware = NULL) const override; // @copydoc - result_t readFromFile(const string& filename, bool verbose, map* defaults, - string* errorDescription, size_t* hash, size_t* size, time_t* time) override; + result_t readFromStream(istream* stream, const string& filename, time_t& mtime, bool verbose, + map* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL) override; // @copydoc result_t addFromFile(const string& filename, unsigned int lineNo, map* row, @@ -1502,9 +1494,6 @@ class MessageMap : public MappedFileReader { /** empty vector for @a getLoadedFiles(). */ static vector s_noFiles; - /** the path to the configuration files. */ - const string m_configPath; - /** whether to add all messages, even if duplicate. */ const bool m_addAll;