]> git.mxchange.org Git - flightgear.git/blob - src/Network/props.cxx
1048a5cc1f116b2dcd1bcd8d64d2dd86491bb57b
[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
40 #include <Main/globals.hxx>
41 #include <Main/viewmgr.hxx>
42
43 #include <plib/netChat.h>
44
45 #include "props.hxx"
46
47 SG_USING_STD(stringstream);
48 SG_USING_STD(ends);
49
50 using std::cout;
51 using std::endl;
52
53 /**
54  * Props connection class.
55  * This class represents a connection to props client.
56  */
57 class PropsChannel : public netChat
58 {
59     netBuffer buffer;
60
61     /**
62      * Current property node name.
63      */
64     string path;
65
66     enum Mode {
67         PROMPT,
68         DATA
69     };
70     Mode mode;
71
72 public:
73     /**
74      * Constructor.
75      */
76     PropsChannel();
77     
78     /**
79      * Append incoming data to our request buffer.
80      *
81      * @param s Character string to append to buffer
82      * @param n Number of characters to append.
83      */
84     void collectIncomingData( const char* s, int n );
85
86     /**
87      * Process a complete request from the props client.
88      */
89     void foundTerminator();
90
91 private:
92     /**
93      * Return a "Node no found" error message to the client.
94      */
95     void node_not_found_error( const string& node_name );
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 /**
119  * 
120  */
121 void
122 PropsChannel::node_not_found_error( const string& node_name )
123 {
124     string error = "-ERR Node \"";
125     error += node_name;
126     error += "\" not found.";
127     push( error.c_str() );
128     push( getTerminator() );
129 }
130
131 // return a human readable form of the value "type"
132 static string
133 getValueTypeString( const SGPropertyNode *node )
134 {
135     string result;
136
137     if ( node == NULL )
138     {
139         return "unknown";
140     }
141
142     SGPropertyNode::Type type = node->getType();
143     if ( type == SGPropertyNode::UNSPECIFIED ) {
144         result = "unspecified";
145     } else if ( type == SGPropertyNode::NONE ) {
146         result = "none";
147     } else if ( type == SGPropertyNode::BOOL ) {
148         result = "bool";
149     } else if ( type == SGPropertyNode::INT ) {
150         result = "int";
151     } else if ( type == SGPropertyNode::LONG ) {
152         result = "long";
153     } else if ( type == SGPropertyNode::FLOAT ) {
154         result = "float";
155     } else if ( type == SGPropertyNode::DOUBLE ) {
156         result = "double";
157     } else if ( type == SGPropertyNode::STRING ) {
158         result = "string";
159     }
160
161     return result;
162 }
163
164 /**
165  * We have a command.
166  * 
167  */
168 void
169 PropsChannel::foundTerminator()
170 {
171     const char* cmd = buffer.getData();
172     SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
173
174     vector<string> tokens = simgear::strutils::split( cmd );
175
176     SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
177
178     if (!tokens.empty()) {
179         string command = tokens[0];
180
181         if (command == "ls") {
182             SGPropertyNode* dir = node;
183             if (tokens.size() == 2) {
184                 if (tokens[1][0] == '/') {
185                     dir = globals->get_props()->getNode( tokens[1].c_str() );
186                 } else {
187                     string s = path;
188                     s += "/";
189                     s += tokens[1];
190                     dir = globals->get_props()->getNode( s.c_str() );
191                 }
192
193                 if (dir == 0) {
194                     node_not_found_error( tokens[1] );
195                     goto prompt;
196                 }
197             }
198
199             for (int i = 0; i < dir->nChildren(); i++) {
200                 SGPropertyNode * child = dir->getChild(i);
201                 string line = child->getDisplayName(true);
202
203                 if ( child->nChildren() > 0 ) {
204                     line += "/";
205                 } else {
206                     if (mode == PROMPT) {
207                         string value = child->getStringValue();
208                         line += " =\t'" + value + "'\t(";
209                         line += getValueTypeString( child );
210                         line += ")";
211                     }
212                 }
213
214                 line += getTerminator();
215                 push( line.c_str() );
216             }
217         } else if ( command == "dump" ) {
218             stringstream buf;
219             if ( tokens.size() <= 1 ) {
220                 writeProperties( buf, node );
221                 buf << ends; // null terminate the string
222                 push( buf.str().c_str() );
223                 push( getTerminator() );
224             } else {
225                 SGPropertyNode *child = node->getNode( tokens[1].c_str() );
226                 if ( child ) {
227                     writeProperties ( buf, child );
228                     buf << ends; // null terminate the string
229                     push( buf.str().c_str() );
230                     push( getTerminator() );
231                 } else {
232                     node_not_found_error( tokens[1] );
233                 }
234             }
235         }
236         else if ( command == "cd" ) {
237             if (tokens.size() == 2) {
238                 try {
239                     SGPropertyNode* child = node->getNode( tokens[1].c_str() );
240                     if ( child ) {
241                         node = child;
242                         path = node->getPath();
243                     } else {
244                         node_not_found_error( tokens[1] );
245                     }
246                 } catch (...) {
247                     // Ignore attempt to move past root node with ".."
248                 }
249             }
250         } else if ( command == "pwd" ) {
251             string ttt = node->getPath();
252             if (ttt.empty()) {
253                 ttt = "/";
254             }
255
256             push( ttt.c_str() );
257             push( getTerminator() );
258         } else if ( command == "get" || command == "show" ) {
259             if ( tokens.size() == 2 ) {
260                 string tmp;     
261                 string value = node->getStringValue ( tokens[1].c_str(), "" );
262                 if ( mode == PROMPT ) {
263                     tmp = tokens[1];
264                     tmp += " = '";
265                     tmp += value;
266                     tmp += "' (";
267                     tmp += getValueTypeString(
268                                      node->getNode( tokens[1].c_str() ) );
269                     tmp += ")";
270                 } else {
271                     tmp = value;
272                 }
273                 push( tmp.c_str() );
274                 push( getTerminator() );
275             }
276         } else if ( command == "set" ) {
277             if ( tokens.size() >= 2 ) {
278                 string value, tmp;
279                 for (unsigned int i = 2; i < tokens.size(); i++) {
280                     if (i > 2)
281                         value += " ";
282                     value += tokens[i];
283                 }
284                 node->getNode( tokens[1].c_str(), true )
285                     ->setStringValue(value.c_str());
286
287                 if ( mode == PROMPT ) {
288                     // now fetch and write out the new value as confirmation
289                     // of the change
290                     value = node->getStringValue ( tokens[1].c_str(), "" );
291                     tmp = tokens[1] + " = '" + value + "' (";
292                     tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
293                     tmp += ")";
294                     push( tmp.c_str() );
295                     push( getTerminator() );
296                 }
297             } 
298         } else if ( command == "reinit" ) {
299             if ( tokens.size() == 2 ) {
300                 string tmp;     
301                 SGPropertyNode args;
302                 for ( unsigned int i = 1; i < tokens.size(); ++i ) {
303                     cout << "props: adding subsystem = " << tokens[i] << endl;
304                     SGPropertyNode *node = args.getNode("subsystem", i-1, true);
305                     node->setStringValue( tokens[i].c_str() );
306                 }
307                 if ( !globals->get_commands()
308                          ->execute( "reinit", &args) )
309                 {
310                     SG_LOG( SG_GENERAL, SG_ALERT,
311                             "Command " << tokens[1] << " failed.");
312                     if ( mode == PROMPT ) {
313                         tmp += "*failed*";
314                         push( tmp.c_str() );
315                         push( getTerminator() );
316                     }
317                 } else {
318                     if ( mode == PROMPT ) {
319                         tmp += "<completed>";
320                         push( tmp.c_str() );
321                         push( getTerminator() );
322                     }
323                 }
324             }
325         } else if ( command == "run" ) {
326             string tmp; 
327             if ( tokens.size() >= 2 ) {
328                 SGPropertyNode args;
329                 if ( tokens[1] == "reinit" ) {
330                     for ( unsigned int i = 2; i < tokens.size(); ++i ) {
331                         cout << "props: adding subsystem = " << tokens[i]
332                              << endl;
333                         SGPropertyNode *node
334                             = args.getNode("subsystem", i-2, true);
335                         node->setStringValue( tokens[i].c_str() );
336                     }
337                 } else if ( tokens[1] == "set-sea-level-air-temp-degc" ) {
338                     for ( unsigned int i = 2; i < tokens.size(); ++i ) {
339                         cout << "props: set-sl command = " << tokens[i]
340                              << endl;
341                         SGPropertyNode *node
342                             = args.getNode("temp-degc", i-2, true);
343                         node->setStringValue( tokens[i].c_str() );
344                     }
345                 } else if ( tokens[1] == "set-outside-air-temp-degc" ) {
346                     for ( unsigned int i = 2; i < tokens.size(); ++i ) {
347                         cout << "props: set-oat command = " << tokens[i]
348                              << endl;
349                         SGPropertyNode *node
350                             = args.getNode("temp-degc", i-2, true);
351                         node->setStringValue( tokens[i].c_str() );
352                     }
353                 } else if ( tokens[1] == "timeofday" ) {
354                     for ( unsigned int i = 2; i < tokens.size(); ++i ) {
355                         cout << "props: time of day command = " << tokens[i]
356                              << endl;
357                         SGPropertyNode *node
358                             = args.getNode("timeofday", i-2, true);
359                         node->setStringValue( tokens[i].c_str() );
360                     }
361                 } else if ( tokens[1] == "play-audio-message" ) {
362                     if ( tokens.size() == 4 ) {
363                         cout << "props: play audio message = " << tokens[2]
364                              << " " << tokens[3] << endl;
365                         SGPropertyNode *node;
366                         node = args.getNode("path", 0, true);
367                         node->setStringValue( tokens[2].c_str() );
368                         node = args.getNode("file", 0, true);
369                         node->setStringValue( tokens[3].c_str() );
370                    }
371                 }
372                 if ( !globals->get_commands()
373                          ->execute(tokens[1].c_str(), &args) )
374                 {
375                     SG_LOG( SG_GENERAL, SG_ALERT,
376                             "Command " << tokens[1] << " failed.");
377                     if ( mode == PROMPT ) {
378                         tmp += "*failed*";
379                         push( tmp.c_str() );
380                         push( getTerminator() );
381                     }
382                 } else {
383                     if ( mode == PROMPT ) {
384                         tmp += "<completed>";
385                         push( tmp.c_str() );
386                         push( getTerminator() );
387                     }
388                 }
389             } else {
390                 if ( mode == PROMPT ) {
391                     tmp += "no command specified";
392                     push( tmp.c_str() );
393                     push( getTerminator() );
394                 }
395             }
396         } else if (command == "quit") {
397             close();
398             shouldDelete();
399             return;
400         } else if ( command == "data" ) {
401             mode = DATA;
402         } else if ( command == "prompt" ) {
403             mode = PROMPT;
404         } else {
405             const char* msg = "\
406 Valid commands are:\r\n\
407 \r\n\
408 cd <dir>           cd to a directory, '..' to move back\r\n\
409 data               switch to raw data mode\r\n\
410 dump               dump current state (in xml)\r\n\
411 get <var>          show the value of a parameter\r\n\
412 help               show this help message\r\n\
413 ls [<dir>]         list directory\r\n\
414 prompt             switch to interactive mode (default)\r\n\
415 pwd                display your current path\r\n\
416 quit               terminate connection\r\n\
417 run <command>      run built in command\r\n\
418 set <var> <val>    set <var> to a new <val>\r\n";
419             push( msg );
420         }
421     }
422
423  prompt:
424     if (mode == PROMPT) {
425         string prompt = node->getPath();
426         if (prompt.empty()) {
427             prompt = "/";
428         }
429         prompt += "> ";
430         push( prompt.c_str() );
431     }
432
433     buffer.remove();
434 }
435
436 /**
437  * 
438  */
439 FGProps::FGProps( const vector<string>& tokens )
440 {
441     // tokens:
442     //   props,port#
443     //   props,medium,direction,hz,hostname,port#,style
444     if (tokens.size() == 2) {
445         port = atoi( tokens[1].c_str() );
446         set_hz( 5 );                // default to processing requests @ 5Hz
447     } else if (tokens.size() == 7) {
448         char* endptr;
449         errno = 0;
450         int hz = strtol( tokens[3].c_str(), &endptr, 10 );
451         if (errno != 0) {
452            SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" );
453            set_hz( 5 );           // default to processing requests @ 5Hz
454         } else {
455             SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to "
456                     << hz << " Hz");
457             set_hz( hz );
458         }
459         port = atoi( tokens[5].c_str() );
460     } else {
461         throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" );
462     }
463 }
464
465 /**
466  * 
467  */
468 FGProps::~FGProps()
469 {
470 }
471
472 /**
473  * 
474  */
475 bool
476 FGProps::open()
477 {
478     if ( is_enabled() )
479     {
480         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " 
481                 << "is already in use, ignoring" );
482         return false;
483     }
484
485     netChannel::open();
486     netChannel::bind( "", port );
487     netChannel::listen( 5 );
488     SG_LOG( SG_IO, SG_INFO, "Props server started on port " << port );
489
490     set_enabled( true );
491     return true;
492 }
493
494 /**
495  * 
496  */
497 bool
498 FGProps::close()
499 {
500     SG_LOG( SG_IO, SG_INFO, "closing FGProps" );   
501     return true;
502 }
503
504 /**
505  * 
506  */
507 bool
508 FGProps::process()
509 {
510     netChannel::poll();
511     return true;
512 }
513
514 /**
515  * 
516  */
517 void
518 FGProps::handleAccept()
519 {
520     netAddress addr;
521     int handle = accept( &addr );
522     SG_LOG( SG_IO, SG_INFO, "Props server accepted connection from "
523             << addr.getHost() << ":" << addr.getPort() );
524     PropsChannel* channel = new PropsChannel();
525     channel->setHandle( handle );
526 }