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