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