X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FNetwork%2Fprops.cxx;h=58905e2f30a8a1c953cf05e017cac6d0239e0de4;hb=b0dcb657e77579ecc79798ff365737095f96f9e2;hp=b54ac4d2912776a9c842610edacfde41ae1ee46e;hpb=e95429572c257fc037eddd88905168598ef37a00;p=flightgear.git diff --git a/src/Network/props.cxx b/src/Network/props.cxx index b54ac4d29..58905e2f3 100644 --- a/src/Network/props.cxx +++ b/src/Network/props.cxx @@ -1,8 +1,10 @@ -// props.hxx -- FGFS property manager interaction class +// \file props.cxx +// Property server class. // // Written by Curtis Olson, started September 2000. +// Modified by Bernie Bright, May 2002. // -// Copyright (C) 2000 Curtis L. Olson - curt@flightgear.org +// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -16,300 +18,621 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ +#ifdef HAVE_CONFIG_H +# include +#endif #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include
+#include -#include // atoi() atof() +#include -#include STL_STRSTREAM +#include #include "props.hxx" -#if !defined(SG_HAVE_NATIVE_SGI_COMPILERS) -SG_USING_STD(cout); -SG_USING_STD(istrstream); -SG_USING_STD(strstream); -#endif +#include +#include +#include + +#include + +using std::stringstream; +using std::ends; + +using std::cout; +using std::endl; + +/** + * Props connection class. + * This class represents a connection to props client. + */ +class PropsChannel : public simgear::NetChat, public SGPropertyChangeListener +{ + simgear::NetBuffer buffer; + + /** + * Current property node name. + */ + string path; + + enum Mode { + PROMPT, + DATA + }; + Mode mode; + +public: + /** + * Constructor. + */ + PropsChannel(); + ~PropsChannel(); + + /** + * Append incoming data to our request buffer. + * + * @param s Character string to append to buffer + * @param n Number of characters to append. + */ + void collectIncomingData( const char* s, int n ); + + /** + * Process a complete request from the props client. + */ + void foundTerminator(); + + // callback for registered listeners (subscriptions) + void valueChanged(SGPropertyNode *node); +private: + + typedef string_list ParameterList; + + inline void node_not_found_error( const string& s ) const { + throw "node '" + s + "' not found"; + } -FGProps::FGProps() { -} + void error(std::string msg) { // wrapper: prints errors to STDERR and to the telnet client + push( msg.c_str() ); push( getTerminator() ); + SG_LOG(SG_NETWORK, SG_ALERT, __FILE__<<"@" << __LINE__ <<" in " << __FUNCTION__ <<":"<< msg.c_str() << std::endl); + } + + + bool check_args(const ParameterList &tok, const unsigned int num, const char* func) { + if (tok.size()-1 < num) { + error(string("Error:Wrong argument count for:")+string(func) ); + return false; + } + return true; + } -FGProps::~FGProps() { + std::vector _listeners; + typedef void (PropsChannel::*TelnetCallback) (const ParameterList&); + std::map callback_map; + + // callback implementations: + void subscribe(const ParameterList &p); + void unsubscribe(const ParameterList &p); +}; + +/** + * + */ +PropsChannel::PropsChannel() + : buffer(512), + path("/"), + mode(PROMPT) +{ + setTerminator( "\r\n" ); + callback_map["subscribe"] = &PropsChannel::subscribe; + callback_map["unsubscribe"] = &PropsChannel::unsubscribe; } +PropsChannel::~PropsChannel() { + // clean up all registered listeners + BOOST_FOREACH(SGPropertyNode_ptr l, _listeners) { + l->removeChangeListener( this ); + } +} -// open hailing frequencies -bool FGProps::open() { - reset(); +void PropsChannel::subscribe(const ParameterList ¶m) { + if (! check_args(param,1,"subscribe")) return; - if ( is_enabled() ) { - SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " - << "is already in use, ignoring" ); - return false; - } + std::string command = param[0]; + const char* p = param[1].c_str(); + if (!p) return; - SGIOChannel *io = get_io_channel(); + //SG_LOG(SG_GENERAL, SG_ALERT, p << std::endl); + push( command.c_str() ); push ( " " ); + push( p ); + push( getTerminator() ); - if ( ! io->open( get_direction() ) ) { - SG_LOG( SG_IO, SG_ALERT, "Error opening channel communication layer." ); - return false; - } + SGPropertyNode *n = globals->get_props()->getNode( p,true ); + if ( n->isTied() ) { + error("Error:Tied properties cannot register listeners"); + return; + } + + if (n) { + n->addChangeListener( this ); + _listeners.push_back( n ); // housekeeping, save for deletion in dtor later on + } else { + error("listener could not be added"); + } +} - set_enabled( true ); - SG_LOG( SG_IO, SG_INFO, "Opening properties channel communication layer." ); +void PropsChannel::unsubscribe(const ParameterList ¶m) { + if (!check_args(param,1,"unsubscribe")) return; - return true; + try { + SGPropertyNode *n = globals->get_props()->getNode( param[1].c_str() ); + if (n) + n->removeChangeListener( this ); + } catch (sg_exception&) { + error("Error:Listener could not be removed"); + } } -// prepare for new connection -bool FGProps::reset() { - path = "/"; - mode = PROMPT; - return true; +//TODO: provide support for different types of subscriptions MODES ? (child added/removed, thesholds, min/max) +void PropsChannel::valueChanged(SGPropertyNode* ptr) { + //SG_LOG(SG_GENERAL, SG_ALERT, __FILE__<< "@"<<__LINE__ << ":" << __FUNCTION__ << std::endl); + std::stringstream response; + response << ptr->getPath(true) << "=" << ptr->getStringValue() << getTerminator(); //TODO: use hashes, echo several properties at once + push( response.str().c_str() ); } +/** + * + */ +void +PropsChannel::collectIncomingData( const char* s, int n ) +{ + buffer.append( s, n ); +} // return a human readable form of the value "type" -static string getValueTypeString( const SGPropertyNode *node ) { +static string +getValueTypeString( const SGPropertyNode *node ) +{ + using namespace simgear; + string result; - if ( node == NULL ) { - return "unknown"; + if ( node == NULL ) + { + return "unknown"; } - SGPropertyNode::Type type = node->getType(); - if ( type == SGPropertyNode::UNSPECIFIED ) { - result = "unspecified"; - } else if ( type == SGPropertyNode::NONE ) { + props::Type type = node->getType(); + if ( type == props::UNSPECIFIED ) { + result = "unspecified"; + } else if ( type == props::NONE ) { result = "none"; - } else if ( type == SGPropertyNode::BOOL ) { - result = "bool"; - } else if ( type == SGPropertyNode::INT ) { - result = "int"; - } else if ( type == SGPropertyNode::LONG ) { - result = "long"; - } else if ( type == SGPropertyNode::FLOAT ) { - result = "float"; - } else if ( type == SGPropertyNode::DOUBLE ) { - result = "double"; - } else if ( type == SGPropertyNode::STRING ) { - result = "string"; + } else if ( type == props::BOOL ) { + result = "bool"; + } else if ( type == props::INT ) { + result = "int"; + } else if ( type == props::LONG ) { + result = "long"; + } else if ( type == props::FLOAT ) { + result = "float"; + } else if ( type == props::DOUBLE ) { + result = "double"; + } else if ( type == props::STRING ) { + result = "string"; } return result; } - -bool FGProps::process_command( const char *cmd ) { - SGIOChannel *io = get_io_channel(); - - cout << "processing command = " << cmd; - string_list tokens; - tokens.clear(); - - istrstream in(cmd); - - while ( !in.eof() ) { - string token; - in >> token; - tokens.push_back( token ); +/** + * We have a command. + * + */ +void +PropsChannel::foundTerminator() +{ + const char* cmd = buffer.getData(); + SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" ); + + ParameterList tokens = simgear::strutils::split( cmd ); + + SGPropertyNode* node = globals->get_props()->getNode( path.c_str() ); + + try { + if (!tokens.empty()) { + string command = tokens[0]; + + if (command == "ls") { + SGPropertyNode* dir = node; + if (tokens.size() == 2) { + if (tokens[1][0] == '/') { + dir = globals->get_props()->getNode( tokens[1].c_str() ); + } else { + string s = path; + s += "/"; + s += tokens[1]; + dir = globals->get_props()->getNode( s.c_str() ); + } + + if (dir == 0) { + node_not_found_error( tokens[1] ); + } + } + + for (int i = 0; i < dir->nChildren(); i++) { + SGPropertyNode * child = dir->getChild(i); + string line = child->getDisplayName(true); + + if ( child->nChildren() > 0 ) { + line += "/"; + } else { + if (mode == PROMPT) { + string value = child->getStringValue(); + line += " =\t'" + value + "'\t("; + line += getValueTypeString( child ); + line += ")"; + } + } + + line += getTerminator(); + push( line.c_str() ); + } + } else if ( command == "dump" ) { + stringstream buf; + if ( tokens.size() <= 1 ) { + writeProperties( buf, node ); + buf << ends; // null terminate the string + push( buf.str().c_str() ); + push( getTerminator() ); + } else { + SGPropertyNode *child = node->getNode( tokens[1].c_str() ); + if ( child ) { + writeProperties ( buf, child ); + buf << ends; // null terminate the string + push( buf.str().c_str() ); + push( getTerminator() ); + } else { + node_not_found_error( tokens[1] ); + } + } + } + else if ( command == "cd" ) { + if (tokens.size() == 2) { + SGPropertyNode* child = node->getNode( tokens[1].c_str() ); + if ( child ) { + node = child; + path = node->getPath(); + } else { + node_not_found_error( tokens[1] ); + } + } + } else if ( command == "pwd" ) { + string pwd = node->getPath(); + if (pwd.empty()) { + pwd = "/"; + } + + push( pwd.c_str() ); + push( getTerminator() ); + } else if ( command == "get" || command == "show" ) { + if ( tokens.size() == 2 ) { + string tmp; + string value = node->getStringValue ( tokens[1].c_str(), "" ); + if ( mode == PROMPT ) { + tmp = tokens[1]; + tmp += " = '"; + tmp += value; + tmp += "' ("; + tmp += getValueTypeString( + node->getNode( tokens[1].c_str() ) ); + tmp += ")"; + } else { + tmp = value; + } + push( tmp.c_str() ); + push( getTerminator() ); + } + } else if ( command == "set" ) { + if ( tokens.size() >= 2 ) { + string value, tmp; + for (unsigned int i = 2; i < tokens.size(); i++) { + if (i > 2) + value += " "; + value += tokens[i]; + } + node->getNode( tokens[1].c_str(), true ) + ->setStringValue(value.c_str()); + + if ( mode == PROMPT ) { + // now fetch and write out the new value as confirmation + // of the change + value = node->getStringValue ( tokens[1].c_str(), "" ); + tmp = tokens[1] + " = '" + value + "' ("; + tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) ); + tmp += ")"; + push( tmp.c_str() ); + push( getTerminator() ); + } + } + } else if ( command == "reinit" ) { + if ( tokens.size() == 2 ) { + string tmp; + SGPropertyNode args; + for ( unsigned int i = 1; i < tokens.size(); ++i ) { + cout << "props: adding subsystem = " << tokens[i] << endl; + SGPropertyNode *node = args.getNode("subsystem", i-1, true); + node->setStringValue( tokens[i].c_str() ); + } + if ( !globals->get_commands() + ->execute( "reinit", &args) ) + { + SG_LOG( SG_NETWORK, SG_ALERT, + "Command " << tokens[1] << " failed."); + if ( mode == PROMPT ) { + tmp += "*failed*"; + push( tmp.c_str() ); + push( getTerminator() ); + } + } else { + if ( mode == PROMPT ) { + tmp += ""; + push( tmp.c_str() ); + push( getTerminator() ); + } + } + } + } else if ( command == "run" ) { + string tmp; + if ( tokens.size() >= 2 ) { + SGPropertyNode args; + if ( tokens[1] == "reinit" ) { + for ( unsigned int i = 2; i < tokens.size(); ++i ) { + cout << "props: adding subsystem = " << tokens[i] + << endl; + SGPropertyNode *node + = args.getNode("subsystem", i-2, true); + node->setStringValue( tokens[i].c_str() ); + } + } else if ( tokens[1] == "set-sea-level-air-temp-degc" ) { + for ( unsigned int i = 2; i < tokens.size(); ++i ) { + cout << "props: set-sl command = " << tokens[i] + << endl; + SGPropertyNode *node + = args.getNode("temp-degc", i-2, true); + node->setStringValue( tokens[i].c_str() ); + } + } else if ( tokens[1] == "set-outside-air-temp-degc" ) { + for ( unsigned int i = 2; i < tokens.size(); ++i ) { + cout << "props: set-oat command = " << tokens[i] + << endl; + SGPropertyNode *node + = args.getNode("temp-degc", i-2, true); + node->setStringValue( tokens[i].c_str() ); + } + } else if ( tokens[1] == "timeofday" ) { + for ( unsigned int i = 2; i < tokens.size(); ++i ) { + cout << "props: time of day command = " << tokens[i] + << endl; + SGPropertyNode *node + = args.getNode("timeofday", i-2, true); + node->setStringValue( tokens[i].c_str() ); + } + } else if ( tokens[1] == "play-audio-message" ) { + if ( tokens.size() == 4 ) { + cout << "props: play audio message = " << tokens[2] + << " " << tokens[3] << endl; + SGPropertyNode *node; + node = args.getNode("path", 0, true); + node->setStringValue( tokens[2].c_str() ); + node = args.getNode("file", 0, true); + node->setStringValue( tokens[3].c_str() ); + } + } + if ( !globals->get_commands() + ->execute(tokens[1].c_str(), &args) ) + { + SG_LOG( SG_NETWORK, SG_ALERT, + "Command " << tokens[1] << " failed."); + if ( mode == PROMPT ) { + tmp += "*failed*"; + push( tmp.c_str() ); + push( getTerminator() ); + } + } else { + if ( mode == PROMPT ) { + tmp += ""; + push( tmp.c_str() ); + push( getTerminator() ); + } + } + } else { + if ( mode == PROMPT ) { + tmp += "no command specified"; + push( tmp.c_str() ); + push( getTerminator() ); + } + } + } else if ( command == "quit" || command == "exit" ) { + close(); + shouldDelete(); + return; + } else if ( command == "data" ) { + mode = DATA; + } else if ( command == "prompt" ) { + mode = PROMPT; + } else if (callback_map.find(command) != callback_map.end() ) { + TelnetCallback t = callback_map[ command ]; + if (t) + (this->*t) (tokens); + else + error("No matching callback found for command:"+command); + } + else { + const char* msg = "\ +Valid commands are:\r\n\ +\r\n\ +cd cd to a directory, '..' to move back\r\n\ +data switch to raw data mode\r\n\ +dump dump current state (in xml)\r\n\ +get show the value of a parameter\r\n\ +help show this help message\r\n\ +ls [] list directory\r\n\ +prompt switch to interactive mode (default)\r\n\ +pwd display your current path\r\n\ +quit terminate connection\r\n\ +run run built in command\r\n\ +set set to a new \r\n\ +subscribe subscribe to property changes \r\n\ +unscubscribe unscubscribe from property changes (var must be the property name/path used by subscribe)\r\n"; + push( msg ); + } + } + + } catch ( const string& msg ) { + string error = "-ERR \"" + msg + "\""; + push( error.c_str() ); + push( getTerminator() ); } - string command = tokens[0]; + if ( mode == PROMPT ) { + string prompt = node->getPath(); + if (prompt.empty()) { + prompt = "/"; + } + prompt += "> "; + push( prompt.c_str() ); + } - SGPropertyNode * node = globals->get_props()->getNode(path.c_str()); + buffer.remove(); +} - if ( command == "ls" ) { - SGPropertyNode * dir = node; - if ( tokens.size() > 2 ) { - if ( tokens[1][0] == '/' ) { - dir = globals->get_props()->getNode(tokens[1].c_str()); - } else { - dir = globals->get_props()->getNode((path + "/" + tokens[1]).c_str()); - } - if ( dir == 0 ) { - tokens[1] = "ERR Node \"" + tokens[1] + "\" not found.\n"; - io->writestring( tokens[1].c_str() ); - return true; - } - } - - for (int i = 0; i < (int)dir->nChildren(); i++) { - SGPropertyNode * child = dir->getChild(i); - string name = child->getName(); - string line = name; - if ( dir->getChild(name.c_str(), 1) ) { - char buf[16]; - sprintf(buf, "[%d]", child->getIndex()); - line += buf; - } - if ( child->nChildren() > 0 ) { - line += "/"; - } else { - if ( mode == PROMPT ) { - string value = dir->getStringValue ( name.c_str(), "" ); - line += " =\t'" + value + "'\t("; - line += getValueTypeString( dir->getNode( name.c_str() ) ); - line += ")"; - } - } - line += "\n"; - io->writestring( line.c_str() ); - } - } else if ( command == "dump" ) { - strstream buf; - if ( tokens.size() <= 1 ) { - writeProperties ( buf, node); - io->writestring( buf.str() ); - } - else { - SGPropertyNode *child = node->getNode(tokens[1].c_str()); - if ( child ) { - writeProperties ( buf, child ); - io->writestring( buf.str() ); - } else { - tokens[1] = "ERR Node \"" + tokens[1] + "\" not found.\n"; - io->writestring( tokens[1].c_str() ); - } - } - } else if ( command == "cd" ) { - // string tmp = "current path = " + node.getPath() + "\n"; - // io->writestring( tmp.c_str() ); - - if ( tokens.size() <= 1 ) { - // do nothing - } else { - SGPropertyNode *child = node->getNode(tokens[1].c_str()); - if ( child ) { - node = child; - path = node->getPath(); - } else { - tokens[1] = "ERR Node \"" + tokens[1] + "\" not found.\n"; - io->writestring( tokens[1].c_str() ); - } - } - } else if ( command == "pwd" ) { - string ttt = node->getPath(); - if ( ttt.empty() ) { - ttt = "/"; - } - ttt += "\n"; - io->writestring( ttt.c_str() ); - } else if ( command == "get" || command == "show" ) { - if ( tokens.size() <= 1 ) { - // do nothing - } else { - string tmp; - string value = node->getStringValue ( tokens[1].c_str(), "" ); - if ( mode == PROMPT ) { - //string ttt = "debug = '" + tokens[1] + "'\n"; - //io->writestring( ttt.c_str() ); - - tmp = tokens[1] + " = '" + value + "' ("; - tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) ); - tmp += ")\n"; - } else { - tmp = value + "\n"; - } - io->writestring( tmp.c_str() ); - } - } else if ( command == "set" ) { - if ( tokens.size() <= 2 ) { - // do nothing - } else { - node->getNode( tokens[1].c_str(), true )->setStringValue(tokens[2].c_str()); - - // now fetch and write out the new value as confirmation - // of the change - string value = node->getStringValue ( tokens[1].c_str(), "" ); - string tmp = tokens[1] + " = '" + value + "' ("; - tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) ); - tmp += ")\n"; - - io->writestring( tmp.c_str() ); - } - } else if ( command == "quit" ) { - close(); - reset(); - return true; - } else if ( command == "data" ) { - mode = DATA; - } else if ( command == "prompt" ) { - mode = PROMPT; +/** + * + */ +FGProps::FGProps( const vector& tokens ) +{ + // tokens: + // props,port# + // props,medium,direction,hz,hostname,port#,style + if (tokens.size() == 2) { + port = atoi( tokens[1].c_str() ); + set_hz( 5 ); // default to processing requests @ 5Hz + } else if (tokens.size() == 7) { + char* endptr; + errno = 0; + int hz = strtol( tokens[3].c_str(), &endptr, 10 ); + if (errno != 0) { + SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" ); + set_hz( 5 ); // default to processing requests @ 5Hz + } else { + SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to " + << hz << " Hz"); + set_hz( hz ); + } + port = atoi( tokens[5].c_str() ); } else { - io->writestring( "\n" ); - io->writestring( "Valid commands are:\n" ); - io->writestring( "\n" ); - io->writestring( "help show help message\n" ); - io->writestring( "ls [] list directory\n" ); - io->writestring( "dump dump current state (in xml)\n" ); - io->writestring( "cd cd to a directory, '..' to move back\n" ); - io->writestring( "pwd display your current path\n" ); - io->writestring( "get show the value of a parameter\n" ); - io->writestring( "show synonym for get\n" ); - io->writestring( "set set to a new \n" ); - io->writestring( "data switch to raw data mode\n" ); - io->writestring( "prompt switch to interactive mode (default)\n" ); - io->writestring( "quit terminate connection\n" ); - io->writestring( "\n" ); + throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" ); } +} - if ( mode == PROMPT ) { - string prompt = node->getPath(); - if ( prompt.empty() ) { - prompt = "/"; - } - prompt += "> "; - io->writestring( prompt.c_str() ); - } - return true; +/** + * + */ +FGProps::~FGProps() +{ } +/** + * + */ +bool +FGProps::open() +{ + if ( is_enabled() ) + { + SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " + << "is already in use, ignoring" ); + return false; + } -// process work for this port -bool FGProps::process() { - SGIOChannel *io = get_io_channel(); - char buf[max_cmd_len]; + if (!simgear::NetChannel::open()) + { + SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open network socket."); + return false; + } - // cout << "processing incoming props command" << endl; + int err = simgear::NetChannel::bind( "", port ); + if (err) + { + SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open port #" << port << " - the port is already used (error " << err << ")."); + return false; + } - if ( get_direction() == SG_IO_BI ) { - // cout << " (bi directional)" << endl; - while ( io->readline( buf, max_cmd_len ) > 0 ) { - SG_LOG( SG_IO, SG_ALERT, "Success reading data." ); - process_command( buf ); - } - } else { - SG_LOG( SG_IO, SG_ALERT, - "in or out direction not supported for FGProps." ); + err = simgear::NetChannel::listen( 5 ); + if (err) + { + SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to listen on port #" << port << "(error " << err << ")."); + return false; } + poller.addChannel(this); + + SG_LOG( SG_IO, SG_INFO, "Props server started on port " << port ); + + set_enabled( true ); return true; } +/** + * + */ +bool +FGProps::close() +{ + SG_LOG( SG_IO, SG_INFO, "closing FGProps" ); + return true; +} -// close the channel -bool FGProps::close() { - SGIOChannel *io = get_io_channel(); - - if ( ! io->close() ) { - return false; - } - - cout << "successfully closed channel\n"; - +/** + * + */ +bool +FGProps::process() +{ + poller.poll(); return true; } + +/** + * + */ +void +FGProps::handleAccept() +{ + simgear::IPAddress addr; + int handle = accept( &addr ); + SG_LOG( SG_IO, SG_INFO, "Props server accepted connection from " + << addr.getHost() << ":" << addr.getPort() ); + PropsChannel* channel = new PropsChannel(); + channel->setHandle( handle ); + poller.addChannel( channel ); +}