ioc: revise qsrv 2 prototype

ioc: check for mis-matched onStartSubscription()/onDisableSubscription()

ioc: fix subscription lifetime

ioc: catch exceptions in dbEvent callbacks

ioc: avoid unnecessary virtual

ioc: minor

ioc: fix qsrv -S

ioc: qsrvGroupSourceInit() catch+log

ioc: runOnServer avoid std::function

ioc: cleanup and simplifications.

Avoid some redundant std::map lookups.
Make Group partially const to prevent implicit ctor.

ioc: avoid typedefs only used once

ioc: overhaul Group::show().  shows triggers

ioc: MappingType

ioc: pvxsgl -> pvxgl

ioc: separate group config singleton from server singleton

ioc: remove unnecessary forward declarations

ioc: restructure pvxsInitHook

ioc: qsrv runtime disable by default

ioc: compat w/ older Base

ioc: link pvxsIoc w/ DB libs

ioc: Channel proper detection of invalid PV

ioc: no need to keep vector<dbCommon*> around

ioc: fix initial group update for mappings w/o dbChannel

ioc: redo testing

split out group tests, only run with Base >= 7.0

ioc: minor

ioc: loc_bad_alloc

ioc: avoid symbol/DTYP clash with pva2pva

ioc: test record alias in group json

ioc: test put failure when SPC_NOMOD and DISP=1

ioc: test channel filters

ioc: unnecessary capture

ioc: avoid sharing Value between multiple subscriptions

It is possible to create two subscriptions through the same channel.

ioc: group subscription include queueSize

ioc: eliminate unused atomicMonitor

ioc: consolidate GroupSource::get()

avoid some indirection

ioc: pvRequest override of atomicPutGet

ioc: fix group non-atomic put

ioc: test asTrap hooks

ioc: test putOrder also sets field order

ioc: simplify GroupConfigProcessor::loadConfigFiles()

Also ensure that groupMapMutex is held

ioc: testqgroup cover JSON def.

ioc: dbLoadGroup() use macros

ioc: pvxsl() take integer argument

ioc: display.form and info(Q:form

ioc: "NO_ALARM" -> ""

ioc: use dbServer

at least for informational callbacks.

ioc: consolidate createRequestAndSubscriptionHandlers()

ioc: eliminate ChannelAndLock

properties dbChannel doesn't need a separate DBManyLock

ioc: test that putOrder also controls field order

ioc: MappingType -> MappingInfo

Handle info(Q:time:tag
Add +type:"const"

ioc: cleanup includes

ioc: test dbNotifyCancel()

ioc: inline checkForTrailingCommentsAtEnd()
This commit is contained in:
Michael Davidsaver
2023-02-21 12:10:31 -08:00
parent 832c76d6e4
commit afafa09547
66 changed files with 4109 additions and 3145 deletions
+370 -292
View File
@@ -28,11 +28,18 @@
#include "utilpvt.h"
#include "yajlcallbackhandler.h"
// include last to avoid clash of #define printf with other headers
#include <epicsStdio.h>
namespace pvxs {
namespace ioc {
DEFINE_LOGGER(_logname, "pvxs.ioc.group.processor");
GroupConfigProcessor::GroupConfigProcessor()
:config(IOCGroupConfig::instance())
{}
/**
* Parse group configuration that has been defined in db configuration files.
* This involves extracting info fields named "Q:Group" from the database configuration
@@ -68,41 +75,92 @@ void GroupConfigProcessor::loadConfigFromDb() {
* Get the list of group files configured on the iocServer and convert them to Group Configuration objects.
*/
void GroupConfigProcessor::loadConfigFiles() {
runOnPvxsServer([this](IOCServer* pPvxsServer) {
// get list of group files to load
auto& groupConfigFiles = pPvxsServer->groupConfigFiles;
// take the list of group files to load
auto groupConfigFiles(std::move(config.groupConfigFiles));
// For each file load the configuration file
auto it = groupConfigFiles.begin();
while (it != groupConfigFiles.end()) {
std::string groupConfigFileName(*it);
groupConfigFiles.erase(it++);
// For each file load the configuration file
for(auto& jfile : groupConfigFiles) {
auto& groupConfigFileName = jfile.fname;
// Get contents of group definition file
std::ifstream jsonGroupConfigStream(groupConfigFileName, std::ifstream::in);
if (!jsonGroupConfigStream.is_open()) {
fprintf(stderr, "Error opening \"%s\"\n", groupConfigFileName.c_str());
continue;
}
std::stringstream buffer;
buffer << jsonGroupConfigStream.rdbuf();
auto jsonGroupConfig = buffer.str();
log_debug_printf(_logname, "Process dbGroup file \"%s\"\n", groupConfigFileName.c_str());
try {
parseConfigString(jsonGroupConfig.c_str());
if (!groupProcessingWarnings.empty()) {
fprintf(stderr, "warning(s) from group definition file \"%s\"\n%s\n",
groupConfigFileName.c_str(), groupProcessingWarnings.c_str());
std::ostringstream buffer;
std::string line;
size_t lineno = 0u;
while(std::getline(*jfile.jf, line)) {
lineno++;
if(jfile.handle) {
if(auto exp = macDefExpand(line.c_str(), jfile.handle.get())) {
try{
line = exp;
}catch(...){
free(exp);
throw;
}
free(exp);
} else {
fprintf(stderr, "Error reading \"%s\" line %zu too long\n",
groupConfigFileName.c_str(), lineno);
continue;
}
} catch (std::exception& e) {
throw std::runtime_error(
SB() << "Error reading group definition file \"" << groupConfigFileName << "\"\n" << e.what());
}
buffer<<line<<'\n';
}
});
if(!jfile.jf->eof() || jfile.jf->bad()) {
fprintf(stderr, "Error reading \"%s\"\n", groupConfigFileName.c_str());
continue;
}
log_debug_printf(_logname, "Process dbGroup file \"%s\"\n", groupConfigFileName.c_str());
try {
parseConfigString(buffer.str().c_str());
if (!groupProcessingWarnings.empty()) {
fprintf(stderr, "warning(s) from group definition file \"%s\"\n%s\n",
groupConfigFileName.c_str(), groupProcessingWarnings.c_str());
}
} catch (std::exception& e) {
throw std::runtime_error(
SB() << "Error reading group definition file \"" << groupConfigFileName << "\"\n" << e.what());
}
}
}
void GroupConfigProcessor::validateGroups() {
assert(groupDefinitionMap.empty()); // not yet populated
auto groups(std::move(groupConfigMap));
for(auto& git : groups) {
try {
auto& group = git.second;
for(auto& fit : group.fieldConfigMap) {
auto& field = fit.second;
switch(field.info.type) {
case MappingInfo::Scalar:
case MappingInfo::Plain:
case MappingInfo::Any:
case MappingInfo::Meta:
case MappingInfo::Proc:
if(field.channel.empty()) {
throw std::runtime_error(SB()<<"field "<<fit.first<<" missing required +channel");
}
break;
case MappingInfo::Structure:
case MappingInfo::Const:
if(!field.channel.empty()) {
fprintf(stderr, "Warning: %s.%s ignores +channel:\"%s\"\n",
git.first.c_str(), fit.first.c_str(), field.channel.c_str());
field.channel.clear();
}
break;
}
}
groupConfigMap.emplace(git.first, std::move(git.second));
}catch(std::exception& e){
fprintf(stderr, "Error: ignoring invalid group %s : %s\n", git.first.c_str(), e.what());
}
}
}
/**
@@ -166,6 +224,12 @@ void GroupConfigProcessor::defineFields(GroupDefinition& groupDefinition, const
continue;
}
if(fieldName.empty() && fieldConfig.info.type!=MappingInfo::Meta) {
fprintf(stderr, "%s.%s Error: only +type:\"meta\" map be mapped at struct top\n",
groupName.c_str(), fieldName.c_str());
continue;
}
groupDefinition.fields.emplace_back(fieldConfig, fieldName);
auto& currentField = groupDefinition.fields.back();
@@ -235,10 +299,10 @@ void GroupConfigProcessor::defineTriggers(GroupDefinition& groupDefinition, cons
groupDefinition.hasTriggers = true;
while (std::getline(splitter, trigger, ',')) {
triggers.insert(trigger);
triggers.insert(std::move(trigger));
}
}
groupDefinition.fieldTriggerMap[fieldName] = triggers;
groupDefinition.fieldTriggerMap.emplace(fieldName, std::move(triggers));
}
/**
@@ -291,14 +355,15 @@ GroupConfigProcessor::resolveGroupTriggerReferences(GroupDefinition& groupDefini
const std::string& fieldName = triggerMapEntry.first;
const auto& targets = triggerMapEntry.second;
if (groupDefinition.fieldMap.count(fieldName) == 0) {
auto it(groupDefinition.fieldMap.find(fieldName));
if (it == groupDefinition.fieldMap.end()) {
fprintf(stderr, "Error: Group \"%s\" defines triggers from nonexistent field \"%s\" \n",
groupName.c_str(), fieldName.c_str());
continue;
}
auto& index = groupDefinition.fieldMap[fieldName];
auto& fieldDefinition = groupDefinition.fields[index];
auto index = it->second;
auto& fieldDefinition = groupDefinition.fields.at(index);
log_debug_printf(_logname, " pvxs trigger '%s.%s' -> ", groupName.c_str(), fieldName.c_str());
@@ -329,13 +394,14 @@ void GroupConfigProcessor::defineGroupTriggers(FieldDefinition& fieldDefinition,
}
} else {
// otherwise map to the specific target if it exists
if (groupDefinition.fieldMap.count(triggerName) == 0) {
auto it(groupDefinition.fieldMap.find(triggerName));
if (it == groupDefinition.fieldMap.end()) {
fprintf(stderr, "Error: Group \"%s\" defines triggers to nonexistent field \"%s\" \n",
groupName.c_str(), triggerName.c_str());
continue;
}
auto& index = ((FieldDefinitionMap&)groupDefinition.fieldMap)[triggerName];
auto& targetedField = groupDefinition.fields[index];
auto index = it->second;
auto& targetedField = groupDefinition.fields.at(index);
assert(targetedField.name == triggerName);
// And if it references a PV
@@ -358,50 +424,49 @@ void GroupConfigProcessor::defineGroupTriggers(FieldDefinition& fieldDefinition,
* 3. Build the lockers for each group and field based on their triggers
*/
void GroupConfigProcessor::createGroups() {
runOnPvxsServer([this](IOCServer* pPvxsServer) {
auto& groupMap = pPvxsServer->groupMap;
auto& groupMap = config.groupMap;
// First pass: Create groups and get array capacities
for (auto& groupDefinitionMapEntry: groupDefinitionMap) {
auto& groupName = groupDefinitionMapEntry.first;
auto& groupDefinition = groupDefinitionMapEntry.second;
try {
if (groupMap.count(groupName) != 0) {
throw std::runtime_error("Group name already in use");
}
// Create group
auto& group = groupMap[groupName];
// Set basic group information
group.name = groupName;
group.atomicPutGet = groupDefinition.atomic != False;
group.atomicMonitor = groupDefinition.hasTriggers;
// Initialise the given group's fields from the given group definition
initialiseGroupFields(group, groupDefinition);
} catch (std::exception& e) {
fprintf(stderr, "%s: Error Group not created: %s\n", groupName.c_str(), e.what());
// First pass: Create groups and get array capacities
for (auto& groupDefinitionMapEntry: groupDefinitionMap) {
auto& groupName = groupDefinitionMapEntry.first;
auto& groupDefinition = groupDefinitionMapEntry.second;
try {
// Create group
auto pair = groupMap.emplace(std::piecewise_construct,
std::forward_as_tuple(groupName),
std::forward_as_tuple(groupName,
groupDefinition.atomic != False));
if (!pair.second) {
throw std::runtime_error("Group name already in use");
}
}
auto& group = pair.first->second;
// Second Pass: assemble group's PV structure definitions and db locker
for (auto& groupDefinitionMapEntry: groupDefinitionMap) {
auto& groupName = groupDefinitionMapEntry.first;
auto& groupDefinition = groupDefinitionMapEntry.second;
try {
auto& group = groupMap[groupName];
// Initialise the given group's db locks
initialiseDbLocker(group);
// Initialize the given group's triggers and associated db locks
initialiseTriggers(group, groupDefinition);
// Initialise the given group's value type
initialiseValueTemplate(group, groupDefinition);
} catch (std::exception& e) {
fprintf(stderr, "%s: Error Group not created: %s\n", groupName.c_str(), e.what());
}
// Initialise the given group's fields from the given group definition
initialiseGroupFields(group, groupDefinition);
} catch (std::exception& e) {
fprintf(stderr, "%s: Error Group not created: %s\n", groupName.c_str(), e.what());
}
});
}
// Second Pass: assemble group's PV structure definitions and db locker
for (auto& groupDefinitionMapEntry: groupDefinitionMap) {
auto& groupName = groupDefinitionMapEntry.first;
auto& groupDefinition = groupDefinitionMapEntry.second;
try {
auto it(groupMap.find(groupName));
assert(it!=groupMap.end());
auto& group =it->second;
// Initialise the given group's db locks
initialiseDbLocker(group);
// Initialize the given group's triggers and associated db locks
initialiseTriggers(group, groupDefinition);
// Initialise the given group's value type
initialiseValueTemplate(group, groupDefinition);
} catch (std::exception& e) {
fprintf(stderr, "%s: Error Group not created: %s\n", groupName.c_str(), e.what());
}
}
}
/**
@@ -423,7 +488,7 @@ void GroupConfigProcessor::initialiseGroupFields(Group& group, const GroupDefini
// for each field
for (auto& fieldDefinition: groupDefinition.fields) {
group.fields.emplace_back(fieldDefinition.name, fieldDefinition.channel, fieldDefinition.structureId);
group.fields.emplace_back(fieldDefinition);
}
}
@@ -473,11 +538,13 @@ void GroupConfigProcessor::initialiseValueTemplate(Group& group, const GroupDefi
* @param groupDefinition the group definition
*/
void GroupConfigProcessor::initialiseTriggers(Group& group, const GroupDefinition& groupDefinition) {
std::vector<dbCommon*> references;
// For all fields in the group
for (auto& fieldDefinition: groupDefinition.fields) {
// As long as it has a channel specified
if (!fieldDefinition.channel.empty()) {
auto& field = group[fieldDefinition.name];
references.clear();
// Look at the fields that it triggers
for (auto& referencedFieldName: fieldDefinition.triggerNames) {
auto referencedFieldIt = groupDefinition.fieldMap.find(referencedFieldName);
@@ -487,18 +554,14 @@ void GroupConfigProcessor::initialiseTriggers(Group& group, const GroupDefinitio
// Add new trigger reference
field.triggers.emplace_back(&referencedField);
// Add new lock record
if (referencedField.value.channel) {
field.value.references.emplace_back(referencedField.value.channel->addr.precord);
}
if (referencedField.properties.channel) {
field.properties.references.emplace_back(referencedField.properties.channel->addr.precord);
if (referencedField.value) {
references.emplace_back(referencedField.value->addr.precord);
}
}
}
// Make the locks
field.value.lock = DBManyLock(field.value.references);
field.properties.lock = DBManyLock(field.properties.references);
field.lock = DBManyLock(references);
}
}
}
@@ -514,32 +577,221 @@ void GroupConfigProcessor::addTemplatesForDefinedFields(std::vector<Member>& gro
const GroupDefinition& groupDefinition) {
for (auto& fieldDefinition: groupDefinition.fields) {
auto& field = group[fieldDefinition.name];
if (fieldDefinition.channel.empty()) {
addMembersForId(groupMembers, field);
} else {
auto& type = fieldDefinition.type;
dbChannel* pDbChannel = field.value.channel;
if (type == "meta") {
field.isMeta = true;
addMembersForMetaData(groupMembers, field);
} else if (type == "proc") {
field.allowProc = true;
} else if (type.empty() || type == "scalar") {
addMembersForScalarType(groupMembers, field, pDbChannel);
} else if (type == "plain") {
addMembersForPlainType(groupMembers, field, pDbChannel);
} else if (type == "any") {
addMembersForAnyType(groupMembers, field);
} else if (type == "structure") {
addMembersForStructureType(groupMembers, field);
dbChannel* pDbChannel = field.value;
switch(fieldDefinition.info.type) {
case MappingInfo::Scalar:
addMembersForScalarType(groupMembers, field, pDbChannel);
break;
case MappingInfo::Plain:
addMembersForPlainType(groupMembers, field, pDbChannel);
break;
case MappingInfo::Any:
addMembersForAnyType(groupMembers, field);
break;
case MappingInfo::Meta:
addMembersForMetaData(groupMembers, field);
break;
case MappingInfo::Proc: // nothing to do here
break;
case MappingInfo::Structure:
addMembersForStructureType(groupMembers, field);
break;
case MappingInfo::Const:
{
TypeDef def(field.info.cval);
if(field.fieldName.empty()) { // placing in root
throw std::logic_error("TODO: \"\":{+type:\"const\" ...} not currently supported");
} else {
throw std::runtime_error(std::string("Unknown +type=") + type);
std::vector<Member> newMem({def.as(field.fieldName.leafFieldName())});
setFieldTypeDefinition(groupMembers, field.fieldName, newMem);
}
}
break;
}
}
}
/**
* To process key part of json nodes. This will be followed by a boolean, integer, block, or null
*
* @param parserContext the parser context
* @param key the key
* @param keyLength the length of the key
* @return non-zero if successful
*/
static
int parserCallbackKey(void* parserContext, const unsigned char* key, const size_t keyLength) {
return GroupConfigProcessor::yajlProcess(parserContext, [&key, &keyLength](GroupProcessorContext* self) {
if (keyLength == 0 && self->depth != 2) {
throw std::runtime_error("empty group or key name not allowed");
}
std::string name((const char*)key, keyLength);
if (self->depth == 1) {
self->groupName.swap(name);
} else if (self->depth == 2) {
self->field.swap(name);
} else if (self->depth == 3) {
self->key.swap(name);
} else {
throw std::logic_error("Malformed json group definition: too many nesting levels");
}
return 1;
});
}
/**
* To process null json nodes
*
* @param parserContext the parser context
* @return non-zero if successful
*/
static
int parserCallbackNull(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
self->assign(Value());
return 1;
});
}
/**
* To process boolean json nodes
*
* @param parserContext the parser context
* @param booleanValue the boolean value
* @return non-zero if successful
*/
static
int parserCallbackBoolean(void* parserContext, int booleanValue) {
return GroupConfigProcessor::yajlProcess(parserContext, [&booleanValue](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Bool).create();
value = booleanValue;
self->assign(value);
return 1;
});
}
/**
* To process integer json nodes
*
* @param parserContext the parser context
* @param integerVal the integer value
* @return non-zero if successful
*/
static
int parserCallbackInteger(void* parserContext, long long integerVal) {
return GroupConfigProcessor::yajlProcess(parserContext, [&integerVal](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Int64).create();
value = (int64_t)integerVal;
self->assign(value);
return 1;
});
}
/**
* To process double json nodes
*
* @param parserContext the parser context
* @param doubleVal the double value
* @return non-zero if successful
*/
static
int parserCallbackDouble(void* parserContext, double doubleVal) {
return GroupConfigProcessor::yajlProcess(parserContext, [&doubleVal](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Float64).create();
value = doubleVal;
self->assign(value);
return 1;
});
}
/**
* To process string json nodes
*
* @param parserContext the parser context
* @param stringVal the string value
* @param stringLen the string length
* @return non-zero if successful
*/
static
int parserCallbackString(void* parserContext, const unsigned char* stringVal,
const size_t stringLen) {
return GroupConfigProcessor::yajlProcess(parserContext, [&stringVal, &stringLen](GroupProcessorContext* self) {
std::string val((const char*)stringVal, stringLen);
auto value = pvxs::TypeDef(TypeCode::String).create();
value = val;
self->assign(value);
return 1;
});
}
/**
* To start processing new json blocks
*
* @param parserContext the parser context
* @return non-zero if successful
*/
static
int parserCallbackStartBlock(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
self->depth++;
if (self->depth > 3) {
throw std::runtime_error("Group field def. can't contain Object (too deep)");
}
return 1;
});
}
/**
* To end processing the current json block
*
* @param parserContext the parser context
* @return non-zero if successful
*/
static
int parserCallbackEndBlock(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
assert(self->key.empty()); // cleared in assign()
if (self->depth == 3) {
self->key.clear();
} else if (self->depth == 2) {
self->field.clear();
} else if (self->depth == 1) {
self->groupName.clear();
} else {
throw std::logic_error("Internal error in json parser: invalid depth");
}
self->depth--;
return 1;
});
}
/**
* These are the callbacks designated by yajl for its parser functions
* They must be defined in this order.
* Note that we don't use number, or arrays
*/
static const
yajl_callbacks yajlParserCallbacks{
&parserCallbackNull,
&parserCallbackBoolean,
&parserCallbackInteger,
&parserCallbackDouble,
nullptr, // number
&parserCallbackString,
&parserCallbackStartBlock,
&parserCallbackKey,
&parserCallbackEndBlock,
nullptr, // start_array,
nullptr, // end_array,
};
/**
* Parse the given json string as a group configuration part for the given dbRecord
* name and extract group definition into our groupDefinitionMap
@@ -585,157 +837,6 @@ void GroupConfigProcessor::parseConfigString(const char* jsonGroupDefinition, co
}
}
/**
* To process key part of json nodes. This will be followed by a boolean, integer, block, or null
*
* @param parserContext the parser context
* @param key the key
* @param keyLength the length of the key
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackKey(void* parserContext, const unsigned char* key, const size_t keyLength) {
return GroupConfigProcessor::yajlProcess(parserContext, [&key, &keyLength](GroupProcessorContext* self) {
if (keyLength == 0 && self->depth != 2) {
throw std::runtime_error("empty group or key name not allowed");
}
std::string name((const char*)key, keyLength);
if (self->depth == 1) {
self->groupName.swap(name);
} else if (self->depth == 2) {
self->field.swap(name);
} else if (self->depth == 3) {
self->key.swap(name);
} else {
throw std::logic_error("Malformed json group definition: too many nesting levels");
}
return 1;
});
}
/**
* To process null json nodes
*
* @param parserContext the parser context
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackNull(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
self->assign(Value());
return 1;
});
}
/**
* To process boolean json nodes
*
* @param parserContext the parser context
* @param booleanValue the boolean value
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackBoolean(void* parserContext, int booleanValue) {
return GroupConfigProcessor::yajlProcess(parserContext, [&booleanValue](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Bool).create();
value = booleanValue;
self->assign(value);
return 1;
});
}
/**
* To process integer json nodes
*
* @param parserContext the parser context
* @param integerVal the integer value
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackInteger(void* parserContext, long long integerVal) {
return GroupConfigProcessor::yajlProcess(parserContext, [&integerVal](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Int64).create();
value = (int64_t)integerVal;
self->assign(value);
return 1;
});
}
/**
* To process double json nodes
*
* @param parserContext the parser context
* @param doubleVal the double value
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackDouble(void* parserContext, double doubleVal) {
return GroupConfigProcessor::yajlProcess(parserContext, [&doubleVal](GroupProcessorContext* self) {
auto value = pvxs::TypeDef(TypeCode::Float64).create();
value = doubleVal;
self->assign(value);
return 1;
});
}
/**
* To process string json nodes
*
* @param parserContext the parser context
* @param stringVal the string value
* @param stringLen the string length
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackString(void* parserContext, const unsigned char* stringVal,
const size_t stringLen) {
return GroupConfigProcessor::yajlProcess(parserContext, [&stringVal, &stringLen](GroupProcessorContext* self) {
std::string val((const char*)stringVal, stringLen);
auto value = pvxs::TypeDef(TypeCode::String).create();
value = val;
self->assign(value);
return 1;
});
}
/**
* To start processing new json blocks
*
* @param parserContext the parser context
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackStartBlock(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
self->depth++;
if (self->depth > 3) {
throw std::runtime_error("Group field def. can't contain Object (too deep)");
}
return 1;
});
}
/**
* To end processing the current json block
*
* @param parserContext the parser context
* @return non-zero if successful
*/
int GroupConfigProcessor::parserCallbackEndBlock(void* parserContext) {
return GroupConfigProcessor::yajlProcess(parserContext, [](GroupProcessorContext* self) {
assert(self->key.empty()); // cleared in assign()
if (self->depth == 3) {
self->key.clear();
} else if (self->depth == 2) {
self->field.clear();
} else if (self->depth == 1) {
self->groupName.clear();
} else {
throw std::logic_error("Internal error in json parser: invalid depth");
}
self->depth--;
return 1;
});
}
/**
* Get the info field string from the given dbEntry for the given key.
* If the key is not found then return the given default value.
@@ -755,20 +856,6 @@ const char* GroupConfigProcessor::infoField(DBEntry& dbEntry, const char* key, c
return dbGetInfoString(dbEntry);
}
/**
* Checks to see if there are trailing comments at the end of the line.
* Throws an exception if there are
*
* @param line the line to check
*/
void GroupConfigProcessor::checkForTrailingCommentsAtEnd(const std::string& line) {
size_t idx = line.find_first_not_of(" \t\n\r");
if (idx != std::string::npos) {
// trailing comments not allowed
throw std::runtime_error("Trailing comments are not allowed");
}
}
/**
* Add a scalar field as the prescribed subfield by adding the appropriate members to the given members list
*
@@ -840,29 +927,12 @@ void GroupConfigProcessor::addMembersForStructureType(std::vector<Member>& group
using namespace pvxs::members;
std::vector<Member> newIdMembers(
{ groupField.isArray ? StructA("", groupField.id, {}) : Struct("", groupField.id, {}) });
{ groupField.isArray ? StructA(groupField.name, groupField.id, {}) : Struct(groupField.name, groupField.id, {}) });
// Add ID to the group members at the position determined by group field name
setFieldTypeDefinition(groupMembers, groupField.fieldName, newIdMembers);
}
/**
* Add metadata fields to the prescribed subfield (or top level) by adding the appropriate members to the
* given members list.
*
* @param groupMembers the given group members to update
* @param groupField the group field used to determine the members to add and how to create them
*/
void GroupConfigProcessor::addMembersForId(std::vector<Member>& groupMembers, const Field& groupField) {
using namespace pvxs::members;
std::vector<Member> newMetaMembers({
Struct(groupField.name, groupField.id, {}),
});
// Add metadata to the group members at the position determined by group field name
setFieldTypeDefinition(groupMembers, groupField.fieldName, newMetaMembers);
}
/**
* Add metadata fields to the prescribed subfield (or top level) by adding the appropriate members to the
* given members list.
@@ -903,7 +973,7 @@ TypeDef GroupConfigProcessor::getTypeDefForChannel(const dbChannel* pDbChannel)
bool display = true;
bool control = true;
bool valueAlarm = (dbfType != DBF_STRING);
leaf = nt::NTScalar{ leafCode, display, control, valueAlarm }.build();
leaf = nt::NTScalar{ leafCode, display, control, valueAlarm, true }.build();
}
return leaf;
}
@@ -932,6 +1002,8 @@ TypeDef GroupConfigProcessor::getTypeDefForChannel(const dbChannel* pDbChannel)
* @param groupMembers the group members to add new members to
* @param fieldName The field name to use to determine how to create the members
* @param leafMembers the leaf member or members to place at the leaf of the members tree
* @param isLeaf If true, leafMembers are added to the parent of the leaf.
* If false, leafMembers are added as members of a Struct which is the leaf
*/
void GroupConfigProcessor::setFieldTypeDefinition(std::vector<Member>& groupMembers, const FieldName& fieldName,
const std::vector<Member>& leafMembers, bool isLeaf) {
@@ -948,6 +1020,7 @@ void GroupConfigProcessor::setFieldTypeDefinition(std::vector<Member>& groupMemb
childrenToAdd = leafMembers;
}
// iterate name in reverse order. rightmost -> leftmost, leaf -> root
for (auto componentNumber = fieldName.size(); componentNumber > 0; componentNumber--) {
const auto& component = fieldName[componentNumber - 1];
@@ -1020,7 +1093,12 @@ bool GroupConfigProcessor::yajlParseHelper(std::istream& jsonGroupDefinitionStre
size_t consumed = yajl_get_bytes_consumed(handle);
if (consumed < line.size()) {
checkForTrailingCommentsAtEnd(line.substr(consumed));
size_t idx = line.find_first_not_of(" \t\n\r", consumed);
if (idx != std::string::npos) {
// TODO: detect the end of potentially multi-line comments...
// for now trailing comments not allowed
throw std::runtime_error("Trailing content after } are not allowed");
}
}
#ifndef EPICS_YAJL_VERSION
@@ -1089,8 +1167,8 @@ bool GroupConfigProcessor::yajlParseHelper(std::istream& jsonGroupDefinitionStre
*/
void GroupConfigProcessor::initialiseDbLocker(Group& group) {
for (auto& field: group.fields) {
dbChannel* pValueChannel = field.value.channel;
dbChannel* pPropertiesChannel = field.properties.channel;
dbChannel* pValueChannel = field.value;
dbChannel* pPropertiesChannel = field.properties;
if (pValueChannel) {
group.value.channels.emplace_back(pValueChannel->addr.precord);
}