2 // Property server class.
4 // Written by Curtis Olson, started September 2000.
5 // Modified by Bernie Bright, May 2002.
7 // Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 #include <simgear/compiler.h>
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/structure/commands.hxx>
33 #include <simgear/misc/strutils.hxx>
34 #include <simgear/props/props.hxx>
35 #include <simgear/props/props_io.hxx>
41 #include <Main/globals.hxx>
42 #include <Main/viewmgr.hxx>
44 #include <simgear/io/sg_netChat.hxx>
48 using std::stringstream;
55 * Props connection class.
56 * This class represents a connection to props client.
58 class PropsChannel : public simgear::NetChat
60 simgear::NetBuffer buffer;
63 * Current property node name.
80 * Append incoming data to our request buffer.
82 * @param s Character string to append to buffer
83 * @param n Number of characters to append.
85 void collectIncomingData( const char* s, int n );
88 * Process a complete request from the props client.
90 void foundTerminator();
93 inline void node_not_found_error( const string& s ) const {
94 throw "node '" + s + "' not found";
101 PropsChannel::PropsChannel()
106 setTerminator( "\r\n" );
113 PropsChannel::collectIncomingData( const char* s, int n )
115 buffer.append( s, n );
118 // return a human readable form of the value "type"
120 getValueTypeString( const SGPropertyNode *node )
122 using namespace simgear;
131 props::Type type = node->getType();
132 if ( type == props::UNSPECIFIED ) {
133 result = "unspecified";
134 } else if ( type == props::NONE ) {
136 } else if ( type == props::BOOL ) {
138 } else if ( type == props::INT ) {
140 } else if ( type == props::LONG ) {
142 } else if ( type == props::FLOAT ) {
144 } else if ( type == props::DOUBLE ) {
146 } else if ( type == props::STRING ) {
158 PropsChannel::foundTerminator()
160 const char* cmd = buffer.getData();
161 SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
163 vector<string> tokens = simgear::strutils::split( cmd );
165 SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
168 if (!tokens.empty()) {
169 string command = tokens[0];
171 if (command == "ls") {
172 SGPropertyNode* dir = node;
173 if (tokens.size() == 2) {
174 if (tokens[1][0] == '/') {
175 dir = globals->get_props()->getNode( tokens[1].c_str() );
180 dir = globals->get_props()->getNode( s.c_str() );
184 node_not_found_error( tokens[1] );
188 for (int i = 0; i < dir->nChildren(); i++) {
189 SGPropertyNode * child = dir->getChild(i);
190 string line = child->getDisplayName(true);
192 if ( child->nChildren() > 0 ) {
195 if (mode == PROMPT) {
196 string value = child->getStringValue();
197 line += " =\t'" + value + "'\t(";
198 line += getValueTypeString( child );
203 line += getTerminator();
204 push( line.c_str() );
206 } else if ( command == "dump" ) {
208 if ( tokens.size() <= 1 ) {
209 writeProperties( buf, node );
210 buf << ends; // null terminate the string
211 push( buf.str().c_str() );
212 push( getTerminator() );
214 SGPropertyNode *child = node->getNode( tokens[1].c_str() );
216 writeProperties ( buf, child );
217 buf << ends; // null terminate the string
218 push( buf.str().c_str() );
219 push( getTerminator() );
221 node_not_found_error( tokens[1] );
225 else if ( command == "cd" ) {
226 if (tokens.size() == 2) {
227 SGPropertyNode* child = node->getNode( tokens[1].c_str() );
230 path = node->getPath();
232 node_not_found_error( tokens[1] );
235 } else if ( command == "pwd" ) {
236 string pwd = node->getPath();
242 push( getTerminator() );
243 } else if ( command == "get" || command == "show" ) {
244 if ( tokens.size() == 2 ) {
246 string value = node->getStringValue ( tokens[1].c_str(), "" );
247 if ( mode == PROMPT ) {
252 tmp += getValueTypeString(
253 node->getNode( tokens[1].c_str() ) );
259 push( getTerminator() );
261 } else if ( command == "set" ) {
262 if ( tokens.size() >= 2 ) {
264 for (unsigned int i = 2; i < tokens.size(); i++) {
269 node->getNode( tokens[1].c_str(), true )
270 ->setStringValue(value.c_str());
272 if ( mode == PROMPT ) {
273 // now fetch and write out the new value as confirmation
275 value = node->getStringValue ( tokens[1].c_str(), "" );
276 tmp = tokens[1] + " = '" + value + "' (";
277 tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
280 push( getTerminator() );
283 } else if ( command == "reinit" ) {
284 if ( tokens.size() == 2 ) {
287 for ( unsigned int i = 1; i < tokens.size(); ++i ) {
288 cout << "props: adding subsystem = " << tokens[i] << endl;
289 SGPropertyNode *node = args.getNode("subsystem", i-1, true);
290 node->setStringValue( tokens[i].c_str() );
292 if ( !globals->get_commands()
293 ->execute( "reinit", &args) )
295 SG_LOG( SG_NETWORK, SG_ALERT,
296 "Command " << tokens[1] << " failed.");
297 if ( mode == PROMPT ) {
300 push( getTerminator() );
303 if ( mode == PROMPT ) {
304 tmp += "<completed>";
306 push( getTerminator() );
310 } else if ( command == "run" ) {
312 if ( tokens.size() >= 2 ) {
314 if ( tokens[1] == "reinit" ) {
315 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
316 cout << "props: adding subsystem = " << tokens[i]
319 = args.getNode("subsystem", i-2, true);
320 node->setStringValue( tokens[i].c_str() );
322 } else if ( tokens[1] == "set-sea-level-air-temp-degc" ) {
323 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
324 cout << "props: set-sl command = " << tokens[i]
327 = args.getNode("temp-degc", i-2, true);
328 node->setStringValue( tokens[i].c_str() );
330 } else if ( tokens[1] == "set-outside-air-temp-degc" ) {
331 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
332 cout << "props: set-oat command = " << tokens[i]
335 = args.getNode("temp-degc", i-2, true);
336 node->setStringValue( tokens[i].c_str() );
338 } else if ( tokens[1] == "timeofday" ) {
339 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
340 cout << "props: time of day command = " << tokens[i]
343 = args.getNode("timeofday", i-2, true);
344 node->setStringValue( tokens[i].c_str() );
346 } else if ( tokens[1] == "play-audio-message" ) {
347 if ( tokens.size() == 4 ) {
348 cout << "props: play audio message = " << tokens[2]
349 << " " << tokens[3] << endl;
350 SGPropertyNode *node;
351 node = args.getNode("path", 0, true);
352 node->setStringValue( tokens[2].c_str() );
353 node = args.getNode("file", 0, true);
354 node->setStringValue( tokens[3].c_str() );
357 if ( !globals->get_commands()
358 ->execute(tokens[1].c_str(), &args) )
360 SG_LOG( SG_NETWORK, SG_ALERT,
361 "Command " << tokens[1] << " failed.");
362 if ( mode == PROMPT ) {
365 push( getTerminator() );
368 if ( mode == PROMPT ) {
369 tmp += "<completed>";
371 push( getTerminator() );
375 if ( mode == PROMPT ) {
376 tmp += "no command specified";
378 push( getTerminator() );
381 } else if ( command == "quit" || command == "exit" ) {
385 } else if ( command == "data" ) {
387 } else if ( command == "prompt" ) {
391 Valid commands are:\r\n\
393 cd <dir> cd to a directory, '..' to move back\r\n\
394 data switch to raw data mode\r\n\
395 dump dump current state (in xml)\r\n\
396 get <var> show the value of a parameter\r\n\
397 help show this help message\r\n\
398 ls [<dir>] list directory\r\n\
399 prompt switch to interactive mode (default)\r\n\
400 pwd display your current path\r\n\
401 quit terminate connection\r\n\
402 run <command> run built in command\r\n\
403 set <var> <val> set <var> to a new <val>\r\n";
408 } catch ( const string& msg ) {
409 string error = "-ERR \"" + msg + "\"";
410 push( error.c_str() );
411 push( getTerminator() );
414 if ( mode == PROMPT ) {
415 string prompt = node->getPath();
416 if (prompt.empty()) {
420 push( prompt.c_str() );
429 FGProps::FGProps( const vector<string>& tokens )
433 // props,medium,direction,hz,hostname,port#,style
434 if (tokens.size() == 2) {
435 port = atoi( tokens[1].c_str() );
436 set_hz( 5 ); // default to processing requests @ 5Hz
437 } else if (tokens.size() == 7) {
440 int hz = strtol( tokens[3].c_str(), &endptr, 10 );
442 SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" );
443 set_hz( 5 ); // default to processing requests @ 5Hz
445 SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to "
449 port = atoi( tokens[5].c_str() );
451 throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" );
453 printf( "Property server started on port %d\n", port );
471 SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
472 << "is already in use, ignoring" );
476 simgear::NetChannel::open();
477 simgear::NetChannel::bind( "", port );
478 simgear::NetChannel::listen( 5 );
479 SG_LOG( SG_IO, SG_INFO, "Props server started on port " << port );
491 SG_LOG( SG_IO, SG_INFO, "closing FGProps" );
501 simgear::NetChannel::poll();
509 FGProps::handleAccept()
511 simgear::IPAddress addr;
512 int handle = accept( &addr );
513 SG_LOG( SG_IO, SG_INFO, "Props server accepted connection from "
514 << addr.getHost() << ":" << addr.getPort() );
515 PropsChannel* channel = new PropsChannel();
516 channel->setHandle( handle );