added "-def" option to read and write commands for using inline message definition (fixes #154), added support for replacing messages during load to FileReader and subclasses, added MessageMap::remove(), prepared decode/encode commands
This commit is contained in:
+31
-12
@@ -103,6 +103,7 @@ static struct options opt = {
|
||||
"", // aclFile
|
||||
false, // foreground
|
||||
false, // enableHex
|
||||
false, // enableDefine
|
||||
PID_FILE_NAME, // pidFile
|
||||
8888, // port
|
||||
false, // localOnly
|
||||
@@ -159,7 +160,8 @@ static const char argpdoc[] =
|
||||
#define O_ACLDEF (O_GENSYN+1)
|
||||
#define O_ACLFIL (O_ACLDEF+1)
|
||||
#define O_HEXCMD (O_ACLFIL+1)
|
||||
#define O_PIDFIL (O_HEXCMD+1)
|
||||
#define O_DEFCMD (O_HEXCMD+1)
|
||||
#define O_PIDFIL (O_DEFCMD+1)
|
||||
#define O_LOCAL (O_PIDFIL+1)
|
||||
#define O_HTTPPT (O_LOCAL+1)
|
||||
#define O_HTMLPA (O_HTTPPT+1)
|
||||
@@ -212,6 +214,7 @@ static const struct argp_option argpoptions[] = {
|
||||
{"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE", 0 },
|
||||
{"foreground", 'f', NULL, 0, "Run in foreground", 0 },
|
||||
{"enablehex", O_HEXCMD, NULL, 0, "Enable hex command", 0 },
|
||||
{"enabledefine", O_DEFCMD, NULL, 0, "Enable define command", 0 },
|
||||
{"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PID_FILE_NAME "]", 0 },
|
||||
{"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]", 0 },
|
||||
{"localhost", O_LOCAL, NULL, 0, "Listen for command line connections on 127.0.0.1 interface only", 0 },
|
||||
@@ -433,6 +436,9 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) {
|
||||
case O_HEXCMD: // --enablehex
|
||||
opt->enableHex = true;
|
||||
break;
|
||||
case O_DEFCMD: // --enabledefine
|
||||
opt->enableDefine = true;
|
||||
break;
|
||||
case O_PIDFIL: // --pidfile=/var/run/ebusd.pid
|
||||
if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) {
|
||||
argp_error(state, "invalid pidfile");
|
||||
@@ -802,14 +808,27 @@ static result_t collectConfigFiles(const string& relPath, const string& prefix,
|
||||
}
|
||||
|
||||
DataFieldTemplates* getTemplates(const string& filename) {
|
||||
string path;
|
||||
size_t pos = filename.find_last_of('/');
|
||||
if (pos != string::npos) {
|
||||
path = filename.substr(0, pos);
|
||||
}
|
||||
const auto it = s_templatesByPath.find(path);
|
||||
if (it != s_templatesByPath.end()) {
|
||||
return it->second;
|
||||
if (filename == "*") {
|
||||
unsigned long maxLength = 0;
|
||||
DataFieldTemplates* best = NULL;
|
||||
for (auto it : s_templatesByPath) {
|
||||
if (it.first.size() > maxLength) {
|
||||
best = it.second;
|
||||
}
|
||||
}
|
||||
if (best) {
|
||||
return best;
|
||||
}
|
||||
} else {
|
||||
string path;
|
||||
size_t pos = filename.find_last_of('/');
|
||||
if (pos != string::npos) {
|
||||
path = filename.substr(0, pos);
|
||||
}
|
||||
const auto it = s_templatesByPath.find(path);
|
||||
if (it != s_templatesByPath.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return &s_globalTemplates;
|
||||
}
|
||||
@@ -843,7 +862,7 @@ static bool readTemplates(const string relPath, const string extension, bool ava
|
||||
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);
|
||||
result_t result = loadDefinitionsFromConfigPath(templates, file, verbose, NULL, &errorDescription, true);
|
||||
if (result == RESULT_OK) {
|
||||
logInfo(lf_main, "read templates in %s", logPath.c_str());
|
||||
return true;
|
||||
@@ -929,7 +948,7 @@ void executeInstructions(MessageMap* messages, bool verbose) {
|
||||
}
|
||||
|
||||
result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription) {
|
||||
map<string, string>* defaults, string* errorDescription, bool replace) {
|
||||
istream* stream = NULL;
|
||||
time_t mtime = 0;
|
||||
if (s_configUriPrefix.empty()) {
|
||||
@@ -942,7 +961,7 @@ result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filenam
|
||||
}
|
||||
result_t result;
|
||||
if (stream) {
|
||||
result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription);
|
||||
result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, replace);
|
||||
delete(stream);
|
||||
} else {
|
||||
result = RESULT_ERR_NOTFOUND;
|
||||
|
||||
+5
-2
@@ -65,6 +65,7 @@ struct options {
|
||||
const char* aclFile; //!< ACL file name
|
||||
bool foreground; //!< run in foreground
|
||||
bool enableHex; //!< enable hex command
|
||||
bool enableDefine; //!< enable define command
|
||||
const char* pidFile; //!< PID file name [/var/run/ebusd.pid]
|
||||
uint16_t port; //!< port to listen for command line connections [8888]
|
||||
bool localOnly; //!< listen on 127.0.0.1 interface only
|
||||
@@ -88,7 +89,8 @@ struct options {
|
||||
|
||||
/**
|
||||
* Get the @a DataFieldTemplates for the specified configuration file.
|
||||
* @param filename the full name of the configuration file.
|
||||
* @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name
|
||||
* or the root templates if not available.
|
||||
* @return the @a DataFieldTemplates.
|
||||
*/
|
||||
DataFieldTemplates* getTemplates(const string& filename);
|
||||
@@ -128,10 +130,11 @@ void executeInstructions(MessageMap* messages, bool verbose = false);
|
||||
* @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 replace whether to replace an already existing entry.
|
||||
* @return @a RESULT_OK on success, or an error code.
|
||||
*/
|
||||
result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription);
|
||||
map<string, string>* defaults, string* errorDescription, bool replace = false);
|
||||
|
||||
} // namespace ebusd
|
||||
|
||||
|
||||
+347
-122
@@ -73,7 +73,7 @@ result_t UserList::getFieldMap(const string& preferLanguage, vector<string>* row
|
||||
}
|
||||
|
||||
result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) {
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) {
|
||||
string name = (*row)["name"];
|
||||
string secret = (*row)["secret"];
|
||||
if (name.empty()) {
|
||||
@@ -165,6 +165,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag
|
||||
} else {
|
||||
logError(lf_main, "error registering data handlers");
|
||||
}
|
||||
m_newlyDefinedMessages = opt.enableDefine ? new MessageMap(true) : NULL;
|
||||
}
|
||||
|
||||
MainLoop::~MainLoop() {
|
||||
@@ -199,6 +200,10 @@ MainLoop::~MainLoop() {
|
||||
while ((msg = m_netQueue.pop()) != NULL) {
|
||||
delete msg;
|
||||
}
|
||||
if (m_newlyDefinedMessages) {
|
||||
delete m_newlyDefinedMessages;
|
||||
m_newlyDefinedMessages = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/** the delay for running the update check. */
|
||||
@@ -546,6 +551,19 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte
|
||||
if (cmd == "G" || cmd == "GRAB") {
|
||||
return executeGrab(args, ostream);
|
||||
}
|
||||
if (cmd == "DEFINE") {
|
||||
if (m_newlyDefinedMessages) {
|
||||
return executeDefine(args, ostream);
|
||||
}
|
||||
*ostream << "ERR: command not enabled";
|
||||
return RESULT_OK;
|
||||
}
|
||||
/*if (cmd == "D" || cmd == "DECODE") {
|
||||
return executeDecode(args, ostream);
|
||||
}
|
||||
if (cmd == "E" || cmd == "ENCODE") {
|
||||
return executeEncode(args, ostream);
|
||||
}*/
|
||||
if (cmd == "SCAN") {
|
||||
return executeScan(args, getUserLevels(*user), ostream);
|
||||
}
|
||||
@@ -621,7 +639,7 @@ result_t MainLoop::executeAuth(const vector<string>& args, string* user, ostring
|
||||
|
||||
result_t MainLoop::executeRead(const vector<string>& args, const string& levels, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
bool hex = false, numeric = false, valueName = false;
|
||||
bool hex = false, newDefinition = false, numeric = false, valueName = false;
|
||||
OutputFormat verbosity = 0;
|
||||
time_t maxAge = 5*60;
|
||||
string circuit, params;
|
||||
@@ -630,8 +648,27 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-h") {
|
||||
hex = true;
|
||||
} else if (args[argPos] == "-def") {
|
||||
if (!m_newlyDefinedMessages) {
|
||||
*ostream << "ERR: command not enabled";
|
||||
return RESULT_OK;
|
||||
}
|
||||
newDefinition = true;
|
||||
} else if (args[argPos] == "-f") {
|
||||
maxAge = 0;
|
||||
} else if (args[argPos] == "-m") {
|
||||
argPos++;
|
||||
if (args.size() > argPos) {
|
||||
result_t result;
|
||||
maxAge = parseInt(args[argPos].c_str(), 10, 0, 24*60*60, &result);
|
||||
if (result != RESULT_OK) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
} else if (args[argPos] == "-v") {
|
||||
switch (verbosity) {
|
||||
case 0:
|
||||
@@ -653,19 +690,6 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
} else if (args[argPos] == "-N") {
|
||||
numeric = true;
|
||||
valueName = true;
|
||||
} else if (args[argPos] == "-m") {
|
||||
argPos++;
|
||||
if (args.size() > argPos) {
|
||||
result_t result;
|
||||
maxAge = parseInt(args[argPos].c_str(), 10, 0, 24*60*60, &result);
|
||||
if (result != RESULT_OK) {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
} else if (args[argPos] == "-c") {
|
||||
argPos++;
|
||||
if (argPos >= args.size()) {
|
||||
@@ -714,15 +738,50 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
}
|
||||
argPos++;
|
||||
}
|
||||
if (hex && (dstAddress != SYN || !circuit.empty() || verbosity != 0 || numeric || pollPriority > 0
|
||||
|| args.size() < argPos + 1)) {
|
||||
if ((hex && (newDefinition || numeric || verbosity != 0 || !circuit.empty() || !params.empty() || dstAddress != SYN
|
||||
|| pollPriority > 0 || args.size() < argPos + 1))
|
||||
|| (newDefinition && (hex || !circuit.empty() || pollPriority > 0 || args.size() != argPos + 1))) {
|
||||
argPos = 0; // print usage
|
||||
}
|
||||
|
||||
if (argPos == 0 || args.size() < argPos + 1 || args.size() > argPos + 2) {
|
||||
*ostream <<
|
||||
"usage: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*]"
|
||||
" NAME [FIELD[.N]]\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*] -def DEFINITION\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Read value(s) or hex message.\n"
|
||||
" -f force reading from the bus (same as '-m 0')\n"
|
||||
" -m SECONDS only return cached value if age is less than SECONDS [300]\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -p PRIO set the message poll priority (1-9)\n"
|
||||
" -v increase verbosity (include names/units/comments)\n"
|
||||
" -V be very verbose (include names, units, and comments)\n"
|
||||
" -n use numeric value of value=name pairs\n"
|
||||
" -N use numeric and named value of value=name pairs\n"
|
||||
" -i VALUE read additional message parameters from VALUE\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" FIELD only retrieve the field named FIELD\n"
|
||||
" N only retrieve the N'th field named FIELD (0-based)\n";
|
||||
if (m_newlyDefinedMessages) {
|
||||
*ostream <<
|
||||
" -def read with explicit message definition:\n"
|
||||
" DEFINITION message definition to use instead of known definition\n";
|
||||
}
|
||||
*ostream <<
|
||||
" -h send hex read message (or answer from cache):\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
return RESULT_OK;
|
||||
}
|
||||
time_t now;
|
||||
time(&now);
|
||||
|
||||
if (hex && argPos > 0) {
|
||||
if (hex) {
|
||||
MasterSymbolString master;
|
||||
result_t ret = parseHexMaster(args, argPos, srcAddress, &master);
|
||||
if (ret != RESULT_OK) {
|
||||
@@ -769,48 +828,22 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
}
|
||||
if (ret >= RESULT_OK) {
|
||||
logInfo(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||
result.str().c_str());
|
||||
result.str().c_str());
|
||||
} else {
|
||||
logError(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||
getResultCode(ret));
|
||||
getResultCode(ret));
|
||||
}
|
||||
*ostream << slave.getStr();
|
||||
return RESULT_OK;
|
||||
}
|
||||
logError(lf_main, "read hex %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||
getResultCode(ret));
|
||||
getResultCode(ret));
|
||||
return ret;
|
||||
}
|
||||
if (argPos == 0 || args.size() < argPos + 1 || args.size() > argPos + 2) {
|
||||
*ostream <<
|
||||
"usage: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N] [-i VALUE[;VALUE]*]"
|
||||
" NAME [FIELD[.N]]\n"
|
||||
" or: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Read value(s) or hex message.\n"
|
||||
" -f force reading from the bus (same as '-m 0')\n"
|
||||
" -m SECONDS only return cached value if age is less than SECONDS [300]\n"
|
||||
" -c CIRCUIT limit to messages of CIRCUIT\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -p PRIO set the message poll priority (1-9)\n"
|
||||
" -v increase verbosity (include names/units/comments)\n"
|
||||
" -V be very verbose (include names, units, and comments)\n"
|
||||
" -n use numeric value of value=name pairs\n"
|
||||
" -N use numeric and named value of value=name pairs\n"
|
||||
" -i VALUE read additional message parameters from VALUE\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" FIELD only retrieve the field named FIELD\n"
|
||||
" N only retrieve the N'th field named FIELD (0-based)\n"
|
||||
" -h send hex read message (or answer from cache):\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
string fieldName;
|
||||
ssize_t fieldIndex = -2;
|
||||
if (args.size() == argPos + 2) {
|
||||
if (!newDefinition && args.size() == argPos + 2) {
|
||||
fieldName = args[argPos + 1];
|
||||
fieldIndex = -1;
|
||||
size_t pos = fieldName.find_last_of('.');
|
||||
@@ -823,44 +856,65 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
}
|
||||
}
|
||||
|
||||
Message* message = m_messages->find(circuit, args[argPos], levels, false);
|
||||
string name;
|
||||
Message* message;
|
||||
result_t ret;
|
||||
if (newDefinition) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
string errorDescription;
|
||||
istringstream istr = istringstream("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||
m_newlyDefinedMessages->clear();
|
||||
ret = m_newlyDefinedMessages->readFromStream(&istr, "temporary", now, true, NULL, &errorDescription);
|
||||
if (ret != RESULT_OK) {
|
||||
*ostream << "ERR: bad definition: " << errorDescription;
|
||||
return RESULT_OK;
|
||||
}
|
||||
deque<Message*> messages;
|
||||
m_newlyDefinedMessages->findAll("", "", levels, false, true, false, false, true, false, 0, 0, &messages);
|
||||
if (messages.empty()) {
|
||||
*ostream << "ERR: bad definition: no read message";
|
||||
return RESULT_OK;
|
||||
}
|
||||
message = *messages.begin();
|
||||
} else {
|
||||
name = args[argPos];
|
||||
message = m_messages->find(circuit, name, levels, false);
|
||||
}
|
||||
// adjust poll priority
|
||||
if (message != NULL && pollPriority > 0 && message->setPollPriority(pollPriority)) {
|
||||
if (!newDefinition && message != NULL && pollPriority > 0 && message->setPollPriority(pollPriority)) {
|
||||
m_messages->addPollMessage(false, message);
|
||||
}
|
||||
verbosity |= valueName ? OF_VALUENAME : numeric ? OF_NUMERIC : 0;
|
||||
result_t ret;
|
||||
if (srcAddress == SYN && dstAddress == SYN && maxAge > 0 && params.length() == 0) {
|
||||
Message* cacheMessage = m_messages->find(circuit, args[argPos], levels, false, true);
|
||||
bool hasCache = cacheMessage != NULL;
|
||||
if (!hasCache || (message != NULL && message->getLastUpdateTime() > cacheMessage->getLastUpdateTime())) {
|
||||
cacheMessage = message; // message is newer/better
|
||||
}
|
||||
if (cacheMessage != NULL
|
||||
&& (cacheMessage->getLastUpdateTime() + maxAge > now
|
||||
|| (cacheMessage->isPassive() && cacheMessage->getLastUpdateTime() != 0))) {
|
||||
if (verbosity & OF_NAMES) {
|
||||
*ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " ";
|
||||
}
|
||||
ret = cacheMessage->decodeLastData(false, fieldIndex == -2 ? NULL : fieldName.c_str(), fieldIndex, verbosity,
|
||||
ostream);
|
||||
if (ret != RESULT_OK) {
|
||||
if (ret < RESULT_OK) {
|
||||
logError(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(),
|
||||
cacheMessage->getName().c_str(), getResultCode(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
logInfo(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(),
|
||||
ostream->str().c_str());
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
if (message == NULL && hasCache) {
|
||||
*ostream << "ERR: no data stored";
|
||||
return RESULT_OK;
|
||||
} // else: read directly from bus
|
||||
bool allowCache = !newDefinition && srcAddress == SYN && dstAddress == SYN && maxAge > 0 && params.length() == 0;
|
||||
Message* cacheMessage = allowCache ? m_messages->find(circuit, name, levels, false, true) : NULL;
|
||||
bool hasCache = cacheMessage != NULL;
|
||||
if (!hasCache || (allowCache && message && message->getLastUpdateTime() > cacheMessage->getLastUpdateTime())) {
|
||||
cacheMessage = message; // message is newer/better
|
||||
}
|
||||
if (cacheMessage && (cacheMessage->getLastUpdateTime() + maxAge > now
|
||||
|| (cacheMessage->isPassive() && cacheMessage->getLastUpdateTime() != 0))) {
|
||||
if (verbosity & OF_NAMES) {
|
||||
*ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " ";
|
||||
}
|
||||
ret = cacheMessage->decodeLastData(false, fieldIndex == -2 ? NULL : fieldName.c_str(), fieldIndex, verbosity,
|
||||
ostream);
|
||||
if (ret != RESULT_OK) {
|
||||
if (ret < RESULT_OK) {
|
||||
logError(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(),
|
||||
cacheMessage->getName().c_str(), getResultCode(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
logInfo(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(),
|
||||
ostream->str().c_str());
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
if (!message && hasCache) {
|
||||
*ostream << "ERR: no data stored";
|
||||
return RESULT_OK;
|
||||
} // else: read directly from bus
|
||||
|
||||
if (message == NULL) {
|
||||
return RESULT_ERR_NOTFOUND;
|
||||
@@ -894,12 +948,18 @@ result_t MainLoop::executeRead(const vector<string>& args, const string& levels,
|
||||
|
||||
result_t MainLoop::executeWrite(const vector<string>& args, const string levels, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
bool hex = false;
|
||||
bool hex = false, newDefinition = false;
|
||||
string circuit;
|
||||
symbol_t srcAddress = SYN, dstAddress = SYN;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-h") {
|
||||
hex = true;
|
||||
} else if (args[argPos] == "-def") {
|
||||
if (!m_newlyDefinedMessages) {
|
||||
*ostream << "ERR: command not enabled";
|
||||
return RESULT_OK;
|
||||
}
|
||||
newDefinition = true;
|
||||
} else if (args[argPos] == "-s" || args[argPos] == "-d") {
|
||||
bool dest = args[argPos] == "-d";
|
||||
argPos++;
|
||||
@@ -931,10 +991,31 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
argPos++;
|
||||
}
|
||||
|
||||
if (hex && (dstAddress != SYN || !circuit.empty() || args.size() < argPos + 1)) {
|
||||
if ((hex && (newDefinition || dstAddress != SYN || !circuit.empty() || args.size() < argPos + 1))
|
||||
|| (newDefinition && (hex || !circuit.empty() || args.size() < argPos + 1 || args.size() > argPos + 2))) {
|
||||
argPos = 0; // print usage
|
||||
}
|
||||
|
||||
if (argPos == 0 || (!newDefinition && (circuit.empty() || (args.size() != argPos + 2 && args.size() != argPos + 1)))) {
|
||||
*ostream << "usage: write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" or: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*]\n"
|
||||
" or: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Write value(s) or hex message.\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -c CIRCUIT CIRCUIT of the message to send\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" VALUE a single field VALUE\n"
|
||||
" -def write with explicit message definition:\n"
|
||||
" DEFINITION message definition to use instead of known definition\n"
|
||||
" -h send hex write message:\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
if (hex && argPos > 0) {
|
||||
MasterSymbolString master;
|
||||
result_t ret = parseHexMaster(args, argPos, srcAddress, &master);
|
||||
@@ -991,23 +1072,29 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (argPos == 0 || circuit.empty() || (args.size() != argPos + 2 && args.size() != argPos + 1)) {
|
||||
*ostream << "usage: write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" or: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" Write value(s) or hex message.\n"
|
||||
" -s QQ override source address QQ\n"
|
||||
" -d ZZ override destination address ZZ\n"
|
||||
" -c CIRCUIT CIRCUIT of the message to send\n"
|
||||
" NAME NAME of the message to send\n"
|
||||
" VALUE a single field VALUE\n"
|
||||
" -h send hex write message:\n"
|
||||
" ZZ destination address\n"
|
||||
" PB SB primary/secondary command byte\n"
|
||||
" NN number of following data bytes\n"
|
||||
" Dx data byte(s) to send";
|
||||
return RESULT_OK;
|
||||
Message* message;
|
||||
result_t ret;
|
||||
if (newDefinition) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
string errorDescription;
|
||||
istringstream istr = istringstream("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||
m_newlyDefinedMessages->clear();
|
||||
ret = m_newlyDefinedMessages->readFromStream(&istr, "temporary", now, true, NULL, &errorDescription);
|
||||
if (ret != RESULT_OK) {
|
||||
*ostream << "ERR: bad definition: " << errorDescription;
|
||||
return RESULT_OK;
|
||||
}
|
||||
deque<Message*> messages;
|
||||
m_newlyDefinedMessages->findAll("", "", levels, false, false, true, false, true, false, 0, 0, &messages);
|
||||
if (messages.empty()) {
|
||||
*ostream << "ERR: bad definition: no write message";
|
||||
return RESULT_OK;
|
||||
}
|
||||
message = *messages.begin();
|
||||
} else {
|
||||
message = m_messages->find(circuit, args[argPos], levels, true);
|
||||
}
|
||||
Message* message = m_messages->find(circuit, args[argPos], levels, true);
|
||||
|
||||
if (message == NULL) {
|
||||
return RESULT_ERR_NOTFOUND;
|
||||
@@ -1016,8 +1103,7 @@ result_t MainLoop::executeWrite(const vector<string>& args, const string levels,
|
||||
return RESULT_ERR_INVALID_ADDR;
|
||||
}
|
||||
// allow missing values
|
||||
result_t ret = m_busHandler->readFromBus(message, args.size() == argPos + 1 ? "" : args[argPos + 1], dstAddress,
|
||||
srcAddress);
|
||||
ret = m_busHandler->readFromBus(message, args.size() == argPos + 1 ? "" : args[argPos + 1], dstAddress, srcAddress);
|
||||
if (ret != RESULT_OK) {
|
||||
logError(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(),
|
||||
getResultCode(ret));
|
||||
@@ -1385,6 +1471,139 @@ result_t MainLoop::executeGrab(const vector<string>& args, ostringstream* ostrea
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
result_t MainLoop::executeDefine(const vector<string>& args, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
bool replace = false;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-r") {
|
||||
replace = true;
|
||||
} else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
argPos++;
|
||||
}
|
||||
|
||||
if (argPos == 0 || args.size() != argPos + 1) {
|
||||
*ostream <<
|
||||
"usage: define [-r] DEFINITION\n"
|
||||
" Define a new message.\n"
|
||||
" -r replace an already existing definition\n"
|
||||
" DEFINITION message definition to add";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
string errorDescription;
|
||||
istringstream istr = istringstream("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||
return m_messages->readFromStream(&istr, "temporary", now, true, NULL, &errorDescription, replace);
|
||||
}
|
||||
|
||||
|
||||
/*result_t MainLoop::executeDecode(const vector<string>& args, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
bool numeric = false, valueName = false;
|
||||
OutputFormat verbosity = 0;
|
||||
while (args.size() > argPos && args[argPos][0] == '-') {
|
||||
if (args[argPos] == "-v") {
|
||||
switch (verbosity) {
|
||||
case 0:
|
||||
verbosity = OF_NAMES;
|
||||
break;
|
||||
case OF_NAMES:
|
||||
verbosity |= OF_UNITS;
|
||||
break;
|
||||
case OF_NAMES|OF_UNITS:
|
||||
verbosity |= OF_COMMENTS;
|
||||
break;
|
||||
}
|
||||
} else if (args[argPos] == "-vv") {
|
||||
verbosity |= OF_NAMES|OF_UNITS;
|
||||
} else if (args[argPos] == "-vvv" || args[argPos] == "-V") {
|
||||
verbosity |= OF_NAMES|OF_UNITS|OF_COMMENTS;
|
||||
} else if (args[argPos] == "-n") {
|
||||
numeric = true;
|
||||
} else if (args[argPos] == "-N") {
|
||||
numeric = true;
|
||||
valueName = true;
|
||||
} else {
|
||||
argPos = 0; // print usage
|
||||
break;
|
||||
}
|
||||
argPos++;
|
||||
}
|
||||
if (args.size() < argPos + 2) {
|
||||
argPos = 0; // print usage
|
||||
}
|
||||
|
||||
if (argPos == 0 || args.size() != argPos + 2) {
|
||||
*ostream <<
|
||||
"usage: decode [-v|-V] [-n|-N] DEFINITION Dx\n"
|
||||
" Decode a field by definition and hex data.\n"
|
||||
" -v increase verbosity (include names/units/comments)\n"
|
||||
" -V be very verbose (include names, units, and comments)\n"
|
||||
" -n use numeric value of value=name pairs\n"
|
||||
" -N use numeric and named value of value=name pairs\n"
|
||||
" DEFINITION field definition (type,divisor/values,unit,comment)\n"
|
||||
" Dx data byte(s) to decode";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
verbosity |= valueName ? OF_VALUENAME : numeric ? OF_NUMERIC : 0;
|
||||
istringstream istr = istringstream("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||
string errorDescription;
|
||||
DataFieldTemplates* templates = getTemplates("*");
|
||||
LoadableDataFieldSet fields("", templates);
|
||||
result_t ret = fields.readFromStream(&istr, "temporary", now, true, NULL, &errorDescription);
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
SlaveSymbolString slave;
|
||||
slave.push_back(0); // dummy length
|
||||
ret = slave.parseHex(args[argPos+1]);
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
slave[0] = slave.size() - 1; // adjust length
|
||||
return fields.read(slave, 0, false, NULL, -1, verbosity, -1, ostream);
|
||||
}
|
||||
|
||||
|
||||
result_t MainLoop::executeEncode(const vector<string>& args, ostringstream* ostream) {
|
||||
size_t argPos = 1;
|
||||
if (argPos == 0 || args.size() != argPos + 2) {
|
||||
*ostream <<
|
||||
"usage: encode DEFINITION VALUE\n"
|
||||
" Encode a field by definition and decoded value.\n"
|
||||
" DEFINITION field definition (type,divisor/values,unit,comment)\n"
|
||||
" VALUE single field VALUE to encode";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
istringstream istr = istringstream("#\n" + args[argPos]); // ensure first line is not used for determining col names
|
||||
string errorDescription;
|
||||
DataFieldTemplates* templates = getTemplates("*");
|
||||
LoadableDataFieldSet fields("", templates);
|
||||
result_t ret = fields.readFromStream(&istr, "temporary", now, true, NULL, &errorDescription);
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
istr = istringstream(args[argPos+1]);
|
||||
SlaveSymbolString slave;
|
||||
ret = fields.write(FIELD_SEPARATOR, 0, &istr, &slave, NULL);
|
||||
if (ret != RESULT_OK) {
|
||||
return ret;
|
||||
}
|
||||
*ostream << slave.getStr(1);
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
|
||||
result_t MainLoop::executeScan(const vector<string>& args, const string& levels, ostringstream* ostream) {
|
||||
if (args.size() == 1) {
|
||||
result_t result = m_busHandler->startScan(false, levels);
|
||||
@@ -1564,28 +1783,34 @@ result_t MainLoop::executeQuit(const vector<string>& args, bool *connected, ostr
|
||||
|
||||
result_t MainLoop::executeHelp(ostringstream* ostream) {
|
||||
*ostream << "usage:\n"
|
||||
" read|r Read value(s): read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N]"
|
||||
" read|r Read value(s): read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-c CIRCUIT] [-p PRIO] [-v|-V] [-n|-N]"
|
||||
" [-i VALUE[;VALUE]*] NAME [FIELD[.N]]\n"
|
||||
" Read hex message: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" write|w Write value(s): write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" Write hex message: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" auth|a Authenticate user: auth USER SECRET\n"
|
||||
" hex Send hex data: hex [-s QQ] ZZPBSBNNDx\n"
|
||||
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
|
||||
" Read by new defintion: read [-f] [-m SECONDS] [-s QQ] [-d ZZ] [-v|-V] [-n|-N]"
|
||||
" [-i VALUE[;VALUE]*] -def DEFINITION\n"
|
||||
" Read hex message: read [-f] [-m SECONDS] [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" write|w Write value(s): write [-s QQ] [-d ZZ] -c CIRCUIT NAME [VALUE[;VALUE]*]\n"
|
||||
" Write by new def.: write [-s QQ] [-d ZZ] -def DEFINITION [VALUE[;VALUE]*]\n"
|
||||
" Write hex message: write [-s QQ] [-c CIRCUIT] -h ZZPBSBNNDx\n"
|
||||
" auth|a Authenticate user: auth USER SECRET\n"
|
||||
" hex Send hex data: hex [-s QQ] ZZPBSBNNDx\n"
|
||||
" find|f Find message(s): find [-v|-V] [-r] [-w] [-p] [-a] [-d] [-h] [-i ID] [-f] [-F COL[,COL]*] [-e]"
|
||||
" [-c CIRCUIT] [-l LEVEL] [NAME]\n"
|
||||
" listen|l Listen for updates: listen [stop]\n"
|
||||
" state|s Report bus state\n"
|
||||
" info|i Report information about the daemon, the configuration, and seen devices.\n"
|
||||
" grab|g Grab messages: grab [stop]\n"
|
||||
" Report the messages: grab result [all]\n"
|
||||
" scan Scan slaves: scan [full|ZZ]\n"
|
||||
" Report scan result: scan result\n"
|
||||
" log Set log area level: log [AREA[,AREA]* LEVEL]\n"
|
||||
" raw Toggle logging of messages or each byte.\n"
|
||||
" dump Toggle binary dump of received bytes\n"
|
||||
" reload Reload CSV config files\n"
|
||||
" quit|q Close connection\n"
|
||||
" help|? Print help help [COMMAND], COMMMAND ?";
|
||||
" listen|l Listen for updates: listen [stop]\n"
|
||||
" state|s Report bus state\n"
|
||||
" info|i Report information about the daemon, the configuration, and seen devices.\n"
|
||||
" grab|g Grab messages: grab [stop]\n"
|
||||
" Report the messages: grab result [all]\n"
|
||||
" define Define new message: define [-r] DEFINITION\n"
|
||||
//" decode|d Decode a field: decode [-v|-V] [-n|-N] DEFINITION Dx\n"
|
||||
//" encode|e Encode a field: encode DEFINITION VALUE\n"
|
||||
" scan Scan slaves: scan [full|ZZ]\n"
|
||||
" Report scan result: scan result\n"
|
||||
" log Set log area level: log [AREA[,AREA]* LEVEL]\n"
|
||||
" raw Toggle logging of messages or each byte.\n"
|
||||
" dump Toggle binary dump of received bytes\n"
|
||||
" reload Reload CSV config files\n"
|
||||
" quit|q Close connection\n"
|
||||
" help|? Print help help [COMMAND], COMMMAND ?";
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
|
||||
+28
-1
@@ -66,7 +66,7 @@ class UserList : public UserInfo, public MappedFileReader {
|
||||
|
||||
// @copydoc
|
||||
result_t addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) override;
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) override;
|
||||
|
||||
// @copydoc
|
||||
bool hasUser(const string& user) const override {
|
||||
@@ -240,6 +240,30 @@ class MainLoop : public Thread, DeviceListener {
|
||||
*/
|
||||
result_t executeGrab(const vector<string>& args, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the define command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
result_t executeDefine(const vector<string>& args, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the decode command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
//result_t executeDecode(const vector<string>& args, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the encode command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
* @param ostream the @a ostringstream to format the result string to.
|
||||
* @return the result code.
|
||||
*/
|
||||
//result_t executeEncode(const vector<string>& args, ostringstream* ostream);
|
||||
|
||||
/**
|
||||
* Execute the scan command.
|
||||
* @param args the arguments passed to the command (starting with the command itself), or empty for help.
|
||||
@@ -373,6 +397,9 @@ class MainLoop : public Thread, DeviceListener {
|
||||
/** whether to enable the hex command. */
|
||||
const bool m_enableHex;
|
||||
|
||||
/** the MessageMap for handling newly defined messages for testing (if enabled), or NULL. */
|
||||
MessageMap* m_newlyDefinedMessages;
|
||||
|
||||
/** set to true to shutdown. */
|
||||
bool m_shutdown;
|
||||
|
||||
|
||||
@@ -1130,7 +1130,7 @@ result_t DataFieldSet::write(char separator, size_t offset, istringstream* input
|
||||
|
||||
|
||||
DataFieldTemplates::DataFieldTemplates(const DataFieldTemplates& other)
|
||||
: MappedFileReader::MappedFileReader(false) {
|
||||
: MappedFileReader::MappedFileReader(false) {
|
||||
for (const auto it : other.m_fieldsByName) {
|
||||
m_fieldsByName[it.first] = it.second->clone();
|
||||
}
|
||||
@@ -1250,7 +1250,7 @@ result_t DataFieldTemplates::getFieldMap(const string& preferLanguage, vector<st
|
||||
}
|
||||
|
||||
result_t DataFieldTemplates::addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) {
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) {
|
||||
string name = (*row)["name"]; // required
|
||||
string firstFieldName;
|
||||
size_t colon = name.find(':');
|
||||
@@ -1271,7 +1271,7 @@ result_t DataFieldTemplates::addFromFile(const string& filename, unsigned int li
|
||||
if (result != RESULT_OK) {
|
||||
return result;
|
||||
}
|
||||
result = add(field, name, true);
|
||||
result = add(field, name, replace);
|
||||
if (result == RESULT_ERR_DUPLICATE_NAME) {
|
||||
*errorDescription = name;
|
||||
}
|
||||
|
||||
+1
-1
@@ -746,7 +746,7 @@ class DataFieldTemplates : public MappedFileReader {
|
||||
|
||||
// @copydoc
|
||||
result_t addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) override;
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) override;
|
||||
|
||||
/**
|
||||
* Gets the template @a DataField instance with the specified name.
|
||||
|
||||
@@ -60,7 +60,7 @@ istream* FileReader::openFile(const string& filename, string* errorDescription,
|
||||
}
|
||||
|
||||
result_t FileReader::readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription, size_t* hash, size_t* size) {
|
||||
map<string, string>* defaults, string* errorDescription, bool replace, size_t* hash, size_t* size) {
|
||||
if (hash) {
|
||||
*hash = 0;
|
||||
}
|
||||
@@ -71,20 +71,20 @@ result_t FileReader::readFromStream(istream* stream, const string& filename, con
|
||||
vector<string> row;
|
||||
result_t result = RESULT_OK;
|
||||
while (stream->peek() != EOF && result == RESULT_OK) {
|
||||
result = readLineFromStream(stream, filename, verbose, &lineNo, &row, errorDescription, hash, size);
|
||||
result = readLineFromStream(stream, filename, verbose, &lineNo, &row, errorDescription, replace, hash, size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result_t FileReader::readLineFromStream(istream* stream, const string& filename, bool verbose,
|
||||
unsigned int* lineNo, vector<string>* row, string* errorDescription, size_t* hash, size_t* size) {
|
||||
unsigned int* lineNo, vector<string>* row, string* errorDescription, bool replace, size_t* hash, size_t* size) {
|
||||
result_t result;
|
||||
if (!splitFields(stream, row, lineNo, hash, size)) {
|
||||
*errorDescription = "blank line";
|
||||
result = RESULT_ERR_EOF;
|
||||
} else {
|
||||
*errorDescription = "";
|
||||
result = addFromFile(filename, *lineNo, row, errorDescription);
|
||||
result = addFromFile(filename, *lineNo, row, errorDescription, replace);
|
||||
}
|
||||
if (result != RESULT_OK) {
|
||||
if (!errorDescription->empty()) {
|
||||
@@ -242,7 +242,7 @@ const string MappedFileReader::normalizeLanguage(const string& lang) {
|
||||
}
|
||||
|
||||
result_t MappedFileReader::readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription, size_t* hash, size_t* size) {
|
||||
map<string, string>* defaults, string* errorDescription, bool replace, size_t* hash, size_t* size) {
|
||||
m_mutex.lock();
|
||||
m_columnNames.clear();
|
||||
m_lastDefaults.clear();
|
||||
@@ -253,13 +253,14 @@ result_t MappedFileReader::readFromStream(istream* stream, const string& filenam
|
||||
size_t lastSep = filename.find_last_of('/');
|
||||
string defaultsPart = lastSep == string::npos ? filename : filename.substr(lastSep+1);
|
||||
extractDefaultsFromFilename(defaultsPart, &m_lastDefaults[""]);
|
||||
result_t result = FileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, hash, size);
|
||||
result_t result
|
||||
= FileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, replace, hash, size);
|
||||
m_mutex.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
result_t MappedFileReader::addFromFile(const string& filename, unsigned int lineNo, vector<string>* row,
|
||||
string* errorDescription) {
|
||||
string* errorDescription, bool replace) {
|
||||
result_t result;
|
||||
if (lineNo == 1) { // first line defines column names
|
||||
result = getFieldMap(m_preferLanguage, row, errorDescription);
|
||||
@@ -325,7 +326,7 @@ result_t MappedFileReader::addFromFile(const string& filename, unsigned int line
|
||||
if (isDefault) {
|
||||
return addDefaultFromFile(filename, lineNo, &rowMapped, &subRowsMapped, errorDescription);
|
||||
}
|
||||
return addFromFile(filename, lineNo, &rowMapped, &subRowsMapped, errorDescription);
|
||||
return addFromFile(filename, lineNo, &rowMapped, &subRowsMapped, errorDescription, replace);
|
||||
}
|
||||
|
||||
const string MappedFileReader::combineRow(const map<string, string>& row) {
|
||||
|
||||
@@ -92,12 +92,14 @@ class FileReader {
|
||||
* @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 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 NULL.
|
||||
* @param size optional pointer to a @a size_t value for storing the normalized size of the file, or NULL.
|
||||
* @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<string, string>* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL);
|
||||
map<string, string>* defaults, string* errorDescription, bool replace = false, size_t* hash = NULL,
|
||||
size_t* size = NULL);
|
||||
|
||||
/**
|
||||
* Read a single line definition from the stream.
|
||||
@@ -107,12 +109,13 @@ class FileReader {
|
||||
* @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 NULL.
|
||||
* @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(istream* stream, const string& filename, bool verbose,
|
||||
unsigned int* lineNo, vector<string>* row, string* errorDescription, size_t* hash, size_t* size);
|
||||
unsigned int* lineNo, vector<string>* row, string* errorDescription, bool replace, size_t* hash, size_t* size);
|
||||
|
||||
/**
|
||||
* Add a definition that was read from a file.
|
||||
@@ -120,10 +123,11 @@ class FileReader {
|
||||
* @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<string>* row,
|
||||
string* errorDescription) = 0;
|
||||
string* errorDescription, bool replace) = 0;
|
||||
|
||||
/**
|
||||
* Left and right trim the string.
|
||||
@@ -205,7 +209,8 @@ class MappedFileReader : public FileReader {
|
||||
|
||||
// @copydoc
|
||||
result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL) override;
|
||||
map<string, string>* defaults, string* errorDescription, bool replace = false, size_t* hash = NULL,
|
||||
size_t* size = NULL) override;
|
||||
|
||||
/**
|
||||
* Extract default values from the file name.
|
||||
@@ -223,7 +228,7 @@ class MappedFileReader : public FileReader {
|
||||
|
||||
// @copydoc
|
||||
result_t addFromFile(const string& filename, unsigned int lineNo, vector<string>* row,
|
||||
string* errorDescription) override;
|
||||
string* errorDescription, bool replace) override;
|
||||
|
||||
/**
|
||||
* Get the field mapping from the given first line.
|
||||
@@ -258,10 +263,11 @@ class MappedFileReader : public FileReader {
|
||||
* @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<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) = 0;
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace = false) = 0;
|
||||
|
||||
/**
|
||||
* @return a reference to all previously extracted default values by type and field name.
|
||||
|
||||
+107
-13
@@ -82,7 +82,7 @@ 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<string, string>* defaults, string* errorDescription);
|
||||
map<string, string>* defaults, string* errorDescription, bool replace = false);
|
||||
|
||||
|
||||
Message::Message(const string& circuit, const string& level, const string& name,
|
||||
@@ -1733,22 +1733,35 @@ result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) {
|
||||
|
||||
vector<string> MessageMap::s_noFiles;
|
||||
|
||||
result_t MessageMap::add(bool storeByName, Message* message) {
|
||||
result_t MessageMap::add(bool storeByName, Message* message, bool replace) {
|
||||
uint64_t key = message->getKey();
|
||||
bool conditional = message->isConditional();
|
||||
if (!m_addAll) {
|
||||
lock();
|
||||
const auto keyIt = m_messagesByKey.find(key);
|
||||
if (keyIt != m_messagesByKey.end()) {
|
||||
Message* other = getFirstAvailable(keyIt->second, message);
|
||||
if (other != NULL) {
|
||||
if (!conditional) {
|
||||
return RESULT_ERR_DUPLICATE; // duplicate key
|
||||
if (replace) {
|
||||
vector<Message*> removeMessages;
|
||||
for (auto other : keyIt->second) {
|
||||
if (!other || !message->checkId(*other)) {
|
||||
continue;
|
||||
}
|
||||
if (!conditional || !other->isConditional() || other->m_condition == message->m_condition) {
|
||||
removeMessages.push_back(other);
|
||||
}
|
||||
}
|
||||
if (!other->isConditional()) {
|
||||
for (auto other : removeMessages) {
|
||||
remove(other);
|
||||
}
|
||||
} else {
|
||||
Message *other = getFirstAvailable(keyIt->second, message);
|
||||
if (other != NULL && (!conditional || !other->isConditional())) {
|
||||
unlock();
|
||||
return RESULT_ERR_DUPLICATE; // duplicate key
|
||||
}
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
bool isPassive = message->isPassive();
|
||||
if (storeByName) {
|
||||
@@ -1763,13 +1776,29 @@ result_t MessageMap::add(bool storeByName, Message* message) {
|
||||
string suffix = FIELD_SEPARATOR + name + (isPassive ? "P" : (isWrite ? "W" : "R"));
|
||||
string nameKey = circuit + suffix;
|
||||
if (!m_addAll) {
|
||||
lock();
|
||||
const auto nameIt = m_messagesByName.find(nameKey);
|
||||
if (nameIt != m_messagesByName.end()) {
|
||||
vector<Message*>* messages = &nameIt->second;
|
||||
if (!message->isConditional() || !messages->front()->isConditional()) {
|
||||
if (replace) {
|
||||
vector<Message*> removeMessages;
|
||||
for (auto other : *messages) {
|
||||
if (!other) {
|
||||
continue;
|
||||
}
|
||||
if (!conditional || !other->isConditional() || other->m_condition == message->m_condition) {
|
||||
removeMessages.push_back(other);
|
||||
}
|
||||
}
|
||||
for (auto other : removeMessages) {
|
||||
remove(other);
|
||||
}
|
||||
} else if (!conditional || !messages->front()->isConditional()) {
|
||||
unlock();
|
||||
return RESULT_ERR_DUPLICATE_NAME; // duplicate key
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
m_messagesByName[nameKey].push_back(message);
|
||||
nameKey = suffix; // also store without circuit
|
||||
@@ -1808,6 +1837,71 @@ result_t MessageMap::add(bool storeByName, Message* message) {
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
void MessageMap::remove(Message* message) {
|
||||
if (message == NULL) {
|
||||
return;
|
||||
}
|
||||
lock();
|
||||
uint64_t key = message->getKey();
|
||||
bool conditional = message->isConditional();
|
||||
const auto keyIt = m_messagesByKey.find(key);
|
||||
bool deleted = false;
|
||||
if (keyIt != m_messagesByKey.end()) {
|
||||
vector<Message*> messages = keyIt->second;
|
||||
for (auto it = messages.begin(); it != messages.end(); ) {
|
||||
Message* other = *it;
|
||||
if (other == message) {
|
||||
if (!deleted) {
|
||||
deleted = true;
|
||||
delete(other);
|
||||
}
|
||||
messages.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (messages.empty()) {
|
||||
m_messagesByKey.erase(keyIt);
|
||||
}
|
||||
}
|
||||
bool storedByName = false;
|
||||
for (auto nameIt = m_messagesByName.begin(); nameIt != m_messagesByName.end(); ) {
|
||||
vector<Message*> messages = nameIt->second;
|
||||
for (auto it = messages.begin(); it != messages.end(); ) {
|
||||
Message* other = *it;
|
||||
if (other == message) {
|
||||
storedByName = true;
|
||||
if (!deleted) {
|
||||
deleted = true;
|
||||
delete(other);
|
||||
}
|
||||
messages.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (messages.empty()) {
|
||||
m_messagesByName.erase(nameIt);
|
||||
} else {
|
||||
++nameIt;
|
||||
}
|
||||
}
|
||||
if (storedByName) {
|
||||
bool isPassive = message->isPassive();
|
||||
m_messageCount--;
|
||||
if (conditional) {
|
||||
m_conditionalMessageCount--;
|
||||
}
|
||||
if (isPassive) {
|
||||
m_passiveMessageCount--;
|
||||
}
|
||||
}
|
||||
if (message->getPollPriority() > 0) {
|
||||
m_pollMessages.remove(message);
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
result_t MessageMap::getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const {
|
||||
// type,circuit,name,[comment],[QQ],ZZ,PBSB,[ID],*name,[part],type,divisor/values,unit,comment
|
||||
// minimum: type,name,PBSB,*type
|
||||
@@ -2105,7 +2199,7 @@ bool MessageMap::extractDefaultsFromFilename(const string& filename, map<string,
|
||||
}
|
||||
|
||||
result_t MessageMap::readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription, size_t* hash, size_t* size) {
|
||||
map<string, string>* defaults, string* errorDescription, bool replace, size_t* hash, size_t* size) {
|
||||
size_t localHash, localSize;
|
||||
if (!hash) {
|
||||
hash = &localHash;
|
||||
@@ -2113,8 +2207,8 @@ result_t MessageMap::readFromStream(istream* stream, const string& filename, con
|
||||
if (!size) {
|
||||
size = &localSize;
|
||||
}
|
||||
result_t result = MappedFileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, hash,
|
||||
size);
|
||||
result_t result
|
||||
= MappedFileReader::readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, replace, hash, size);
|
||||
if (defaults) {
|
||||
string circuit = AttributedItem::pluck("circuit", defaults);
|
||||
if (!circuit.empty() && m_circuitData.find(circuit) == m_circuitData.end()) {
|
||||
@@ -2134,7 +2228,7 @@ result_t MessageMap::readFromStream(istream* stream, const string& filename, con
|
||||
}
|
||||
|
||||
result_t MessageMap::addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) {
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) {
|
||||
Condition* condition = NULL;
|
||||
string types = AttributedItem::pluck("type", row);
|
||||
result_t result = readConditions(filename, &types, errorDescription, &condition);
|
||||
@@ -2188,7 +2282,7 @@ result_t MessageMap::addFromFile(const string& filename, unsigned int lineNo, ma
|
||||
}
|
||||
for (const auto message : messages) {
|
||||
if (result == RESULT_OK) {
|
||||
result = add(true, message);
|
||||
result = add(true, message, replace);
|
||||
if (result == RESULT_ERR_DUPLICATE_NAME) {
|
||||
*errorDescription = "invalid name";
|
||||
} else if (result == RESULT_ERR_DUPLICATE) {
|
||||
|
||||
+23
-3
@@ -782,6 +782,18 @@ class MessagePriorityQueue
|
||||
}
|
||||
priority_queue<Message*, vector<Message*>, compareMessagePriority>::push(__x);
|
||||
}
|
||||
/**
|
||||
* Remove data from the queue.
|
||||
* @param __x the element to remove.
|
||||
*/
|
||||
void remove(const value_type& __x) {
|
||||
for (vector<Message*>::iterator it = c.begin(); it != c.end(); it++) {
|
||||
if (*it == __x) {
|
||||
c.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1233,10 +1245,17 @@ class MessageMap : public MappedFileReader {
|
||||
* Add a @a Message instance to this set.
|
||||
* @param message the @a Message instance to add.
|
||||
* @param storeByName whether to store the @a Message by name.
|
||||
* @param replace whether to replace an already existing entry.
|
||||
* @return @a RESULT_OK on success, or an error code.
|
||||
* Note: the caller may not free the added instance on success.
|
||||
*/
|
||||
result_t add(bool storeByName, Message* message);
|
||||
result_t add(bool storeByName, Message* message, bool replace = false);
|
||||
|
||||
/**
|
||||
* Remove a previously added @a Message.
|
||||
* @param message the @a Message to remove.
|
||||
*/
|
||||
void remove(Message* message);
|
||||
|
||||
// @copydoc
|
||||
result_t getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const override;
|
||||
@@ -1261,11 +1280,12 @@ class MessageMap : public MappedFileReader {
|
||||
|
||||
// @copydoc
|
||||
result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose,
|
||||
map<string, string>* defaults, string* errorDescription, size_t* hash = NULL, size_t* size = NULL) override;
|
||||
map<string, string>* defaults, string* errorDescription, bool replace = false, size_t* hash = NULL,
|
||||
size_t* size = NULL) override;
|
||||
|
||||
// @copydoc
|
||||
result_t addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
|
||||
vector< map<string, string> >* subRows, string* errorDescription) override;
|
||||
vector< map<string, string> >* subRows, string* errorDescription, bool replace) override;
|
||||
|
||||
/**
|
||||
* Get the scan @a Message instance for the specified address.
|
||||
|
||||
Reference in New Issue
Block a user