]> git.mxchange.org Git - flightgear.git/blob - src/Network/props.cxx
Fix crash on exit when FGCom is not being used.
[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 #include <simgear/structure/exception.hxx>
37
38 #include <sstream>
39 #include <iostream>
40 #include <errno.h>
41
42 #include <Main/globals.hxx>
43 #include <Viewer/viewmgr.hxx>
44
45 #include <simgear/io/sg_netChat.hxx>
46
47 #include <simgear/misc/strutils.hxx>
48
49 #include "props.hxx"
50
51 #include <map>
52 #include <vector>
53 #include <string>
54
55 #include <boost/foreach.hpp>
56
57 using std::stringstream;
58 using std::ends;
59
60 using std::cout;
61 using std::endl;
62
63 /**
64  * Props connection class.
65  * This class represents a connection to props client.
66  */
67 class PropsChannel : public simgear::NetChat, public SGPropertyChangeListener
68 {
69     simgear::NetBuffer buffer;
70
71     /**
72      * Current property node name.
73      */
74     string path;
75
76     enum Mode {
77         PROMPT,
78         DATA
79     };
80     Mode mode;
81
82 public:
83     /**
84      * Constructor.
85      */
86     PropsChannel();
87     ~PropsChannel();
88
89     /**
90      * Append incoming data to our request buffer.
91      *
92      * @param s Character string to append to buffer
93      * @param n Number of characters to append.
94      */
95     void collectIncomingData( const char* s, int n );
96
97     /**
98      * Process a complete request from the props client.
99      */
100     void foundTerminator();
101
102     // callback for registered listeners (subscriptions)
103     void valueChanged(SGPropertyNode *node);
104 private:
105
106     typedef string_list ParameterList;
107
108     inline void node_not_found_error( const string& s ) const {
109         throw "node '" + s + "' not found";
110     }
111
112     void error(std::string msg) {  // wrapper: prints errors to STDERR and to the telnet client
113             push( msg.c_str() ); push( getTerminator() );
114             SG_LOG(SG_NETWORK, SG_ALERT, __FILE__<<"@" << __LINE__ <<" in " << __FUNCTION__ <<":"<< msg.c_str() << std::endl);
115     }
116
117
118     bool check_args(const ParameterList &tok, const unsigned int num, const char* func) {
119             if (tok.size()-1 < num) {
120                     error(string("Error:Wrong argument count for:")+string(func) );
121                     return false;
122             }
123             return true;
124     }
125
126     std::vector<SGPropertyNode_ptr> _listeners;
127     typedef void (PropsChannel::*TelnetCallback) (const ParameterList&);
128     std::map<std::string, TelnetCallback> callback_map;
129
130     // callback implementations:
131     void subscribe(const ParameterList &p);
132     void unsubscribe(const ParameterList &p);
133 };
134
135 /**
136  *
137  */
138 PropsChannel::PropsChannel()
139     : buffer(512),
140       path("/"),
141       mode(PROMPT)
142 {
143     setTerminator( "\r\n" );
144     callback_map["subscribe"]   =       &PropsChannel::subscribe;
145     callback_map["unsubscribe"] =       &PropsChannel::unsubscribe;
146 }
147
148 PropsChannel::~PropsChannel() {
149   // clean up all registered listeners
150   BOOST_FOREACH(SGPropertyNode_ptr l, _listeners) { 
151     l->removeChangeListener( this  );
152  }
153 }
154
155 void PropsChannel::subscribe(const ParameterList &param) {
156         if (! check_args(param,1,"subscribe")) return;
157
158         std::string command = param[0];
159         const char* p = param[1].c_str();
160         if (!p) return;
161
162   //SG_LOG(SG_GENERAL, SG_ALERT, p << std::endl);
163   push( command.c_str() ); push ( " " );
164   push( p );
165   push( getTerminator() );
166
167   SGPropertyNode *n = globals->get_props()->getNode( p,true );
168         if ( n->isTied() ) { 
169                 error("Error:Tied properties cannot register listeners"); 
170                 return;
171         }
172   
173         if (n) {
174     n->addChangeListener( this );
175          _listeners.push_back( n ); // housekeeping, save for deletion in dtor later on
176   } else {
177                  error("listener could not be added");
178   }
179 }
180
181 void PropsChannel::unsubscribe(const ParameterList &param) {
182   if (!check_args(param,1,"unsubscribe")) return;
183
184   try {
185    SGPropertyNode *n = globals->get_props()->getNode( param[1].c_str() );
186    if (n)
187     n->removeChangeListener( this );
188   } catch (sg_exception&) {
189           error("Error:Listener could not be removed");
190   }
191 }
192
193
194 //TODO: provide support for different types of subscriptions MODES ? (child added/removed, thesholds, min/max)
195 void PropsChannel::valueChanged(SGPropertyNode* ptr) {
196   //SG_LOG(SG_GENERAL, SG_ALERT, __FILE__<< "@"<<__LINE__ << ":" << __FUNCTION__ << std::endl);  
197   std::stringstream response;
198   response << ptr->getPath(true) << "=" <<  ptr->getStringValue() << getTerminator(); //TODO: use hashes, echo several properties at once
199   push( response.str().c_str() );
200 }
201
202 /**
203  *
204  */
205 void
206 PropsChannel::collectIncomingData( const char* s, int n )
207 {
208     buffer.append( s, n );
209 }
210
211 // return a human readable form of the value "type"
212 static string
213 getValueTypeString( const SGPropertyNode *node )
214 {
215     using namespace simgear;
216
217     string result;
218
219     if ( node == NULL )
220     {
221         return "unknown";
222     }
223
224     props::Type type = node->getType();
225     if ( type == props::UNSPECIFIED ) {
226         result = "unspecified";
227     } else if ( type == props::NONE ) {
228         result = "none";
229     } else if ( type == props::BOOL ) {
230         result = "bool";
231     } else if ( type == props::INT ) {
232         result = "int";
233     } else if ( type == props::LONG ) {
234         result = "long";
235     } else if ( type == props::FLOAT ) {
236         result = "float";
237     } else if ( type == props::DOUBLE ) {
238         result = "double";
239     } else if ( type == props::STRING ) {
240         result = "string";
241     }
242
243     return result;
244 }
245
246 /**
247  * We have a command.
248  *
249  */
250 void
251 PropsChannel::foundTerminator()
252 {
253     const char* cmd = buffer.getData();
254     SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
255
256     ParameterList tokens = simgear::strutils::split( cmd );
257
258     SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
259
260     try {
261         if (!tokens.empty()) {
262             string command = tokens[0];
263
264             if (command == "ls") {
265                 SGPropertyNode* dir = node;
266                 if (tokens.size() == 2) {
267                     if (tokens[1][0] == '/') {
268                         dir = globals->get_props()->getNode( tokens[1].c_str() );
269                     } else {
270                         string s = path;
271                         s += "/";
272                         s += tokens[1];
273                         dir = globals->get_props()->getNode( s.c_str() );
274                     }
275
276                     if (dir == 0) {
277                         node_not_found_error( tokens[1] );
278                     }
279                 }
280
281                 for (int i = 0; i < dir->nChildren(); i++) {
282                     SGPropertyNode * child = dir->getChild(i);
283                     string line = child->getDisplayName(true);
284
285                     if ( child->nChildren() > 0 ) {
286                         line += "/";
287                     } else {
288                         if (mode == PROMPT) {
289                             string value = child->getStringValue();
290                             line += " =\t'" + value + "'\t(";
291                             line += getValueTypeString( child );
292                             line += ")";
293                         }
294                     }
295
296                     line += getTerminator();
297                     push( line.c_str() );
298                 }
299             } else if ( command == "dump" ) {
300                 stringstream buf;
301                 if ( tokens.size() <= 1 ) {
302                     writeProperties( buf, node );
303                     buf << ends; // null terminate the string
304                     push( buf.str().c_str() );
305                     push( getTerminator() );
306                 } else {
307                     SGPropertyNode *child = node->getNode( tokens[1].c_str() );
308                     if ( child ) {
309                         writeProperties ( buf, child );
310                         buf << ends; // null terminate the string
311                         push( buf.str().c_str() );
312                         push( getTerminator() );
313                     } else {
314                         node_not_found_error( tokens[1] );
315                     }
316                 }
317             }
318             else if ( command == "cd" ) {
319                 if (tokens.size() == 2) {
320                     SGPropertyNode* child = node->getNode( tokens[1].c_str() );
321                     if ( child ) {
322                         node = child;
323                         path = node->getPath();
324                     } else {
325                         node_not_found_error( tokens[1] );
326                     }
327                 }
328             } else if ( command == "pwd" ) {
329                 string pwd = node->getPath();
330                 if (pwd.empty()) {
331                     pwd = "/";
332                 }
333
334                 push( pwd.c_str() );
335                 push( getTerminator() );
336             } else if ( command == "get" || command == "show" ) {
337                 if ( tokens.size() == 2 ) {
338                     string tmp;
339                     string value = node->getStringValue ( tokens[1].c_str(), "" );
340                     if ( mode == PROMPT ) {
341                         tmp = tokens[1];
342                         tmp += " = '";
343                         tmp += value;
344                         tmp += "' (";
345                         tmp += getValueTypeString(
346                                          node->getNode( tokens[1].c_str() ) );
347                         tmp += ")";
348                     } else {
349                         tmp = value;
350                     }
351                     push( tmp.c_str() );
352                     push( getTerminator() );
353                 }
354             } else if ( command == "set" ) {
355                 if ( tokens.size() >= 2 ) {
356                     string value, tmp;
357                     for (unsigned int i = 2; i < tokens.size(); i++) {
358                         if (i > 2)
359                             value += " ";
360                         value += tokens[i];
361                     }
362                     node->getNode( tokens[1].c_str(), true )
363                         ->setStringValue(value.c_str());
364
365                     if ( mode == PROMPT ) {
366                         // now fetch and write out the new value as confirmation
367                         // of the change
368                         value = node->getStringValue ( tokens[1].c_str(), "" );
369                         tmp = tokens[1] + " = '" + value + "' (";
370                         tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
371                         tmp += ")";
372                         push( tmp.c_str() );
373                         push( getTerminator() );
374                     }
375                 }
376             } else if ( command == "reinit" ) {
377                 if ( tokens.size() == 2 ) {
378                     string tmp;
379                     SGPropertyNode args;
380                     for ( unsigned int i = 1; i < tokens.size(); ++i ) {
381                         cout << "props: adding subsystem = " << tokens[i] << endl;
382                         SGPropertyNode *node = args.getNode("subsystem", i-1, true);
383                         node->setStringValue( tokens[i].c_str() );
384                     }
385                     if ( !globals->get_commands()
386                              ->execute( "reinit", &args) )
387                     {
388                         SG_LOG( SG_NETWORK, SG_ALERT,
389                                 "Command " << tokens[1] << " failed.");
390                         if ( mode == PROMPT ) {
391                             tmp += "*failed*";
392                             push( tmp.c_str() );
393                             push( getTerminator() );
394                         }
395                     } else {
396                         if ( mode == PROMPT ) {
397                             tmp += "<completed>";
398                             push( tmp.c_str() );
399                             push( getTerminator() );
400                         }
401                     }
402                 }
403             } else if ( command == "run" ) {
404                 string tmp;
405                 if ( tokens.size() >= 2 ) {
406                     SGPropertyNode args;
407                     if ( tokens[1] == "reinit" ) {
408                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
409                             cout << "props: adding subsystem = " << tokens[i]
410                                  << endl;
411                             SGPropertyNode *node
412                                 = args.getNode("subsystem", i-2, true);
413                             node->setStringValue( tokens[i].c_str() );
414                         }
415                     } else if ( tokens[1] == "set-sea-level-air-temp-degc" ) {
416                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
417                             cout << "props: set-sl command = " << tokens[i]
418                                  << endl;
419                             SGPropertyNode *node
420                                 = args.getNode("temp-degc", i-2, true);
421                             node->setStringValue( tokens[i].c_str() );
422                         }
423                     } else if ( tokens[1] == "set-outside-air-temp-degc" ) {
424                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
425                             cout << "props: set-oat command = " << tokens[i]
426                                  << endl;
427                             SGPropertyNode *node
428                                 = args.getNode("temp-degc", i-2, true);
429                             node->setStringValue( tokens[i].c_str() );
430                         }
431                     } else if ( tokens[1] == "timeofday" ) {
432                         for ( unsigned int i = 2; i < tokens.size(); ++i ) {
433                             cout << "props: time of day command = " << tokens[i]
434                                  << endl;
435                             SGPropertyNode *node
436                                 = args.getNode("timeofday", i-2, true);
437                             node->setStringValue( tokens[i].c_str() );
438                         }
439                     } else if ( tokens[1] == "play-audio-message" ) {
440                         if ( tokens.size() == 4 ) {
441                             cout << "props: play audio message = " << tokens[2]
442                                  << " " << tokens[3] << endl;
443                             SGPropertyNode *node;
444                             node = args.getNode("path", 0, true);
445                             node->setStringValue( tokens[2].c_str() );
446                             node = args.getNode("file", 0, true);
447                             node->setStringValue( tokens[3].c_str() );
448                        }
449                     }
450                     if ( !globals->get_commands()
451                              ->execute(tokens[1].c_str(), &args) )
452                     {
453                         SG_LOG( SG_NETWORK, SG_ALERT,
454                                 "Command " << tokens[1] << " failed.");
455                         if ( mode == PROMPT ) {
456                             tmp += "*failed*";
457                             push( tmp.c_str() );
458                             push( getTerminator() );
459                         }
460                     } else {
461                         if ( mode == PROMPT ) {
462                             tmp += "<completed>";
463                             push( tmp.c_str() );
464                             push( getTerminator() );
465                         }
466                     }
467                 } else {
468                     if ( mode == PROMPT ) {
469                         tmp += "no command specified";
470                         push( tmp.c_str() );
471                         push( getTerminator() );
472                     }
473                 }
474             } else if ( command == "quit" || command == "exit" ) {
475                 close();
476                 shouldDelete();
477                 return;
478             } else if ( command == "data" ) {
479                 mode = DATA;
480             } else if ( command == "prompt" ) {
481                 mode = PROMPT;
482             } else if (callback_map.find(command) != callback_map.end() ) {
483                    TelnetCallback t = callback_map[ command ]; 
484                    if (t) 
485                            (this->*t) (tokens); 
486                    else
487                            error("No matching callback found for command:"+command);
488             }
489             else {
490                 const char* msg = "\
491 Valid commands are:\r\n\
492 \r\n\
493 cd <dir>           cd to a directory, '..' to move back\r\n\
494 data               switch to raw data mode\r\n\
495 dump               dump current state (in xml)\r\n\
496 get <var>          show the value of a parameter\r\n\
497 help               show this help message\r\n\
498 ls [<dir>]         list directory\r\n\
499 prompt             switch to interactive mode (default)\r\n\
500 pwd                display your current path\r\n\
501 quit               terminate connection\r\n\
502 run <command>      run built in command\r\n\
503 set <var> <val>    set <var> to a new <val>\r\n\
504 subscribe <var>    subscribe to property changes \r\n\
505 unscubscribe <var>  unscubscribe from property changes (var must be the property name/path used by subscribe)\r\n";
506                 push( msg );
507             }
508         }
509
510     } catch ( const string& msg ) {
511         string error = "-ERR \"" + msg + "\"";
512         push( error.c_str() );
513         push( getTerminator() );
514     }
515
516     if ( mode == PROMPT ) {
517         string prompt = node->getPath();
518         if (prompt.empty()) {
519             prompt = "/";
520         }
521         prompt += "> ";
522         push( prompt.c_str() );
523     }
524
525     buffer.remove();
526 }
527
528 /**
529  *
530  */
531 FGProps::FGProps( const vector<string>& tokens )
532 {
533     // tokens:
534     //   props,port#
535     //   props,medium,direction,hz,hostname,port#,style
536     if (tokens.size() == 2) {
537         port = atoi( tokens[1].c_str() );
538         set_hz( 5 );                // default to processing requests @ 5Hz
539     } else if (tokens.size() == 7) {
540         char* endptr;
541         errno = 0;
542         int hz = strtol( tokens[3].c_str(), &endptr, 10 );
543         if (errno != 0) {
544            SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" );
545            set_hz( 5 );           // default to processing requests @ 5Hz
546         } else {
547             SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to "
548                     << hz << " Hz");
549             set_hz( hz );
550         }
551         port = atoi( tokens[5].c_str() );
552     } else {
553         throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" );
554     }
555 }
556
557 /**
558  *
559  */
560 FGProps::~FGProps()
561 {
562 }
563
564 /**
565  *
566  */
567 bool
568 FGProps::open()
569 {
570     if ( is_enabled() )
571     {
572         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
573                 << "is already in use, ignoring" );
574         return false;
575     }
576
577     if (!simgear::NetChannel::open())
578     {
579         SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open network socket.");
580         return false;
581     }
582
583     int err = simgear::NetChannel::bind( "", port );
584     if (err)
585     {
586         SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open port #" << port << " - the port is already used (error " << err << ").");
587         return false;
588     }
589
590     err = simgear::NetChannel::listen( 5 );
591     if (err)
592     {
593         SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to listen on port #" << port << "(error " << err << ").");
594         return false;
595     }
596
597     poller.addChannel(this);
598     
599     SG_LOG( SG_IO, SG_INFO, "Props server started on port " << port );
600
601     set_enabled( true );
602     return true;
603 }
604
605 /**
606  *
607  */
608 bool
609 FGProps::close()
610 {
611     SG_LOG( SG_IO, SG_INFO, "closing FGProps" );
612     return true;
613 }
614
615 /**
616  *
617  */
618 bool
619 FGProps::process()
620 {
621     poller.poll();
622     return true;
623 }
624
625 /**
626  *
627  */
628 void
629 FGProps::handleAccept()
630 {
631     simgear::IPAddress addr;
632     int handle = accept( &addr );
633     SG_LOG( SG_IO, SG_INFO, "Props server accepted connection from "
634             << addr.getHost() << ":" << addr.getPort() );
635     PropsChannel* channel = new PropsChannel();
636     channel->setHandle( handle );
637     poller.addChannel( channel );
638 }