]> git.mxchange.org Git - flightgear.git/blob - src/Network/props.cxx
Make errno dependency explicit, since raw_socket.hxx won't expose it, soon.
[flightgear.git] / src / Network / props.cxx
1 // \file props.cxx
2 // Property server class.
3 //
4 // Written by Curtis Olson, started September 2000.
5 // Modified by Bernie Bright, May 2002.
6 //
7 // Copyright (C) 2000  Curtis L. Olson - http://www.flightgear.org/~curt
8 //
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.
13 //
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.
18 //
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.
22 //
23 // $Id$
24
25
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29
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>
36
37 #include <sstream>
38 #include <iostream>
39 #include <errno.h>
40
41 #include <Main/globals.hxx>
42 #include <Main/viewmgr.hxx>
43
44 #include <simgear/io/sg_netChat.hxx>
45
46 #include "props.hxx"
47
48 using std::stringstream;
49 using std::ends;
50
51 using std::cout;
52 using std::endl;
53
54 /**
55  * Props connection class.
56  * This class represents a connection to props client.
57  */
58 class PropsChannel : public simgear::NetChat
59 {
60     simgear::NetBuffer buffer;
61
62     /**
63      * Current property node name.
64      */
65     string path;
66
67     enum Mode {
68         PROMPT,
69         DATA
70     };
71     Mode mode;
72
73 public:
74     /**
75      * Constructor.
76      */
77     PropsChannel();
78
79     /**
80      * Append incoming data to our request buffer.
81      *
82      * @param s Character string to append to buffer
83      * @param n Number of characters to append.
84      */
85     void collectIncomingData( const char* s, int n );
86
87     /**
88      * Process a complete request from the props client.
89      */
90     void foundTerminator();
91
92 private:
93     inline void node_not_found_error( const string& s ) const {
94         throw "node '" + s + "' not found";
95     }
96 };
97
98 /**
99  *
100  */
101 PropsChannel::PropsChannel()
102     : buffer(512),
103       path("/"),
104       mode(PROMPT)
105 {
106     setTerminator( "\r\n" );
107 }
108
109 /**
110  *
111  */
112 void
113 PropsChannel::collectIncomingData( const char* s, int n )
114 {
115     buffer.append( s, n );
116 }
117
118 // return a human readable form of the value "type"
119 static string
120 getValueTypeString( const SGPropertyNode *node )
121 {
122     using namespace simgear;
123
124     string result;
125
126     if ( node == NULL )
127     {
128         return "unknown";
129     }
130
131     props::Type type = node->getType();
132     if ( type == props::UNSPECIFIED ) {
133         result = "unspecified";
134     } else if ( type == props::NONE ) {
135         result = "none";
136     } else if ( type == props::BOOL ) {
137         result = "bool";
138     } else if ( type == props::INT ) {
139         result = "int";
140     } else if ( type == props::LONG ) {
141         result = "long";
142     } else if ( type == props::FLOAT ) {
143         result = "float";
144     } else if ( type == props::DOUBLE ) {
145         result = "double";
146     } else if ( type == props::STRING ) {
147         result = "string";
148     }
149
150     return result;
151 }
152
153 /**
154  * We have a command.
155  *
156  */
157 void
158 PropsChannel::foundTerminator()
159 {
160     const char* cmd = buffer.getData();
161     SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
162
163     vector<string> tokens = simgear::strutils::split( cmd );
164
165     SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
166
167     try {
168         if (!tokens.empty()) {
169             string command = tokens[0];
170
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() );
176                     } else {
177                         string s = path;
178                         s += "/";
179                         s += tokens[1];
180                         dir = globals->get_props()->getNode( s.c_str() );
181                     }
182
183                     if (dir == 0) {
184                         node_not_found_error( tokens[1] );
185                     }
186                 }
187
188                 for (int i = 0; i < dir->nChildren(); i++) {
189                     SGPropertyNode * child = dir->getChild(i);
190                     string line = child->getDisplayName(true);
191
192                     if ( child->nChildren() > 0 ) {
193                         line += "/";
194                     } else {
195                         if (mode == PROMPT) {
196                             string value = child->getStringValue();
197                             line += " =\t'" + value + "'\t(";
198                             line += getValueTypeString( child );
199                             line += ")";
200                         }
201                     }
202
203                     line += getTerminator();
204                     push( line.c_str() );
205                 }
206             } else if ( command == "dump" ) {
207                 stringstream buf;
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() );
213                 } else {
214                     SGPropertyNode *child = node->getNode( tokens[1].c_str() );
215                     if ( child ) {
216                         writeProperties ( buf, child );
217                         buf << ends; // null terminate the string
218                         push( buf.str().c_str() );
219                         push( getTerminator() );
220                     } else {
221                         node_not_found_error( tokens[1] );
222                     }
223                 }
224             }
225             else if ( command == "cd" ) {
226                 if (tokens.size() == 2) {
227                     SGPropertyNode* child = node->getNode( tokens[1].c_str() );
228                     if ( child ) {
229                         node = child;
230                         path = node->getPath();
231                     } else {
232                         node_not_found_error( tokens[1] );
233                     }
234                 }
235             } else if ( command == "pwd" ) {
236                 string pwd = node->getPath();
237                 if (pwd.empty()) {
238                     pwd = "/";
239                 }
240
241                 push( pwd.c_str() );
242                 push( getTerminator() );
243             } else if ( command == "get" || command == "show" ) {
244                 if ( tokens.size() == 2 ) {
245                     string tmp;
246                     string value = node->getStringValue ( tokens[1].c_str(), "" );
247                     if ( mode == PROMPT ) {
248                         tmp = tokens[1];
249                         tmp += " = '";
250                         tmp += value;
251                         tmp += "' (";
252                         tmp += getValueTypeString(
253                                          node->getNode( tokens[1].c_str() ) );
254                         tmp += ")";
255                     } else {
256                         tmp = value;
257                     }
258                     push( tmp.c_str() );
259                     push( getTerminator() );
260                 }
261             } else if ( command == "set" ) {
262                 if ( tokens.size() >= 2 ) {
263                     string value, tmp;
264                     for (unsigned int i = 2; i < tokens.size(); i++) {
265                         if (i > 2)
266                             value += " ";
267                         value += tokens[i];
268                     }
269                     node->getNode( tokens[1].c_str(), true )
270                         ->setStringValue(value.c_str());
271
272                     if ( mode == PROMPT ) {
273                         // now fetch and write out the new value as confirmation
274                         // of the change
275                         value = node->getStringValue ( tokens[1].c_str(), "" );
276                         tmp = tokens[1] + " = '" + value + "' (";
277                         tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
278                         tmp += ")";
279                         push( tmp.c_str() );
280                         push( getTerminator() );
281                     }
282                 }
283             } else if ( command == "reinit" ) {
284                 if ( tokens.size() == 2 ) {
285                     string tmp;
286                     SGPropertyNode args;
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() );
291                     }
292                     if ( !globals->get_commands()
293                              ->execute( "reinit", &args) )
294                     {
295                         SG_LOG( SG_GENERAL, SG_ALERT,
296                                 "Command " << tokens[1] << " failed.");
297                         if ( mode == PROMPT ) {
298                             tmp += "*failed*";
299                             push( tmp.c_str() );
300                             push( getTerminator() );
301                         }
302                     } else {
303                         if ( mode == PROMPT ) {
304                             tmp += "<completed>";
305                             push( tmp.c_str() );
306                             push( getTerminator() );
307                         }
308                     }
309                 }
310             } else if ( command == "run" ) {
311                 string tmp;
312                 if ( tokens.size() >= 2 ) {
313                     SGPropertyNode args;
314                     if ( tokens[1] == "reinit" ) {
315                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
316                             cout << "props: adding subsystem = " << tokens[i]
317                                  << endl;
318                             SGPropertyNode *node
319                                 = args.getNode("subsystem", i-2, true);
320                             node->setStringValue( tokens[i].c_str() );
321                         }
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]
325                                  << endl;
326                             SGPropertyNode *node
327                                 = args.getNode("temp-degc", i-2, true);
328                             node->setStringValue( tokens[i].c_str() );
329                         }
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]
333                                  << endl;
334                             SGPropertyNode *node
335                                 = args.getNode("temp-degc", i-2, true);
336                             node->setStringValue( tokens[i].c_str() );
337                         }
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]
341                                  << endl;
342                             SGPropertyNode *node
343                                 = args.getNode("timeofday", i-2, true);
344                             node->setStringValue( tokens[i].c_str() );
345                         }
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() );
355                        }
356                     }
357                     if ( !globals->get_commands()
358                              ->execute(tokens[1].c_str(), &args) )
359                     {
360                         SG_LOG( SG_GENERAL, SG_ALERT,
361                                 "Command " << tokens[1] << " failed.");
362                         if ( mode == PROMPT ) {
363                             tmp += "*failed*";
364                             push( tmp.c_str() );
365                             push( getTerminator() );
366                         }
367                     } else {
368                         if ( mode == PROMPT ) {
369                             tmp += "<completed>";
370                             push( tmp.c_str() );
371                             push( getTerminator() );
372                         }
373                     }
374                 } else {
375                     if ( mode == PROMPT ) {
376                         tmp += "no command specified";
377                         push( tmp.c_str() );
378                         push( getTerminator() );
379                     }
380                 }
381             } else if ( command == "quit" || command == "exit" ) {
382                 close();
383                 shouldDelete();
384                 return;
385             } else if ( command == "data" ) {
386                 mode = DATA;
387             } else if ( command == "prompt" ) {
388                 mode = PROMPT;
389             } else {
390                 const char* msg = "\
391 Valid commands are:\r\n\
392 \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";
404                 push( msg );
405             }
406         }
407
408     } catch ( const string& msg ) {
409         string error = "-ERR \"" + msg + "\"";
410         push( error.c_str() );
411         push( getTerminator() );
412     }
413
414     if ( mode == PROMPT ) {
415         string prompt = node->getPath();
416         if (prompt.empty()) {
417             prompt = "/";
418         }
419         prompt += "> ";
420         push( prompt.c_str() );
421     }
422
423     buffer.remove();
424 }
425
426 /**
427  *
428  */
429 FGProps::FGProps( const vector<string>& tokens )
430 {
431     // tokens:
432     //   props,port#
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) {
438         char* endptr;
439         errno = 0;
440         int hz = strtol( tokens[3].c_str(), &endptr, 10 );
441         if (errno != 0) {
442            SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" );
443            set_hz( 5 );           // default to processing requests @ 5Hz
444         } else {
445             SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to "
446                     << hz << " Hz");
447             set_hz( hz );
448         }
449         port = atoi( tokens[5].c_str() );
450     } else {
451         throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" );
452     }
453     printf( "Property server started on port %d\n", port );
454 }
455
456 /**
457  *
458  */
459 FGProps::~FGProps()
460 {
461 }
462
463 /**
464  *
465  */
466 bool
467 FGProps::open()
468 {
469     if ( is_enabled() )
470     {
471         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
472                 << "is already in use, ignoring" );
473         return false;
474     }
475
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 );
480
481     set_enabled( true );
482     return true;
483 }
484
485 /**
486  *
487  */
488 bool
489 FGProps::close()
490 {
491     SG_LOG( SG_IO, SG_INFO, "closing FGProps" );
492     return true;
493 }
494
495 /**
496  *
497  */
498 bool
499 FGProps::process()
500 {
501     simgear::NetChannel::poll();
502     return true;
503 }
504
505 /**
506  *
507  */
508 void
509 FGProps::handleAccept()
510 {
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 );
517 }