]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/PropertyChangeWebsocket.cxx
Interim windows build fix
[flightgear.git] / src / Network / http / PropertyChangeWebsocket.cxx
1 // PropertyChangeWebsocket.cxx -- A websocket for propertychangelisteners
2 //
3 // Written by Torsten Dreyer, started April 2014.
4 //
5 // Copyright (C) 2014  Torsten Dreyer
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #include "PropertyChangeWebsocket.hxx"
22 #include "PropertyChangeObserver.hxx"
23 #include "jsonprops.hxx"
24
25 #include <simgear/debug/logstream.hxx>
26 #include <simgear/props/props.hxx>
27 #include <simgear/structure/commands.hxx>
28
29 #include <simgear/props/props_io.hxx>
30 #include <Main/globals.hxx>
31 #include <Main/fg_props.hxx>
32
33 #include <3rdparty/cjson/cJSON.h>
34
35 namespace flightgear {
36 namespace http {
37
38 using std::string;
39
40 static unsigned nextid = 0;
41
42 static void setPropertyFromJson(SGPropertyNode_ptr prop, cJSON * json)
43 {
44   if (!prop) return;
45   if ( NULL == json ) return;
46   switch ( json->type ) {
47   case cJSON_String:
48     prop->setStringValue(json->valuestring);
49     break;
50     
51   case cJSON_Number:
52     prop->setDoubleValue(json->valuedouble);
53     break;
54     
55   case cJSON_True:
56     prop->setBoolValue(true);
57     break;
58       
59   case cJSON_False:
60     prop->setBoolValue(false);
61     break;
62       
63   default:
64     return;
65   }
66 }
67   
68 static void handleSetCommand(const string_list& nodes, cJSON* json, WebsocketWriter &writer)
69 {
70   cJSON * value = cJSON_GetObjectItem(json, "value");
71   if ( NULL != value ) {
72     if (nodes.size() > 1) {
73       SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: insufficent values for nodes:" << nodes.size());
74       return;
75     }
76     
77     SGPropertyNode_ptr n = fgGetNode(nodes.front());
78     if (!n) {
79       SG_LOG(SG_NETWORK, SG_WARN, "httpd: set '" << nodes.front() << "'  not found");
80       return;
81     }
82     
83     setPropertyFromJson(n, value);
84     return;
85   }
86   
87   cJSON * values = cJSON_GetObjectItem(json, "values");
88   if ( ( NULL == values ) || ( static_cast<size_t>(cJSON_GetArraySize(values)) != nodes.size()) ) {
89     SG_LOG(SG_NETWORK, SG_WARN, "httpd: WS set: mismatched nodes/values sizes:" << nodes.size());
90     return;
91   }
92   
93   string_list::const_iterator it;
94   int i=0;
95   for (it = nodes.begin(); it != nodes.end(); ++it, ++i) {
96     SGPropertyNode_ptr n = fgGetNode(*it);
97     if (!n) {
98       SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "'  not found");
99       return;
100     }
101
102     setPropertyFromJson(n, cJSON_GetArrayItem(values, i));
103   } // of nodes iteration
104 }
105   
106 static void handleExecCommand(cJSON* json)
107 {
108   cJSON* name = cJSON_GetObjectItem(json, "fgcommand");
109   if ((NULL == name )|| (NULL == name->valuestring)) {
110     SG_LOG(SG_NETWORK, SG_WARN, "httpd: exec: no fgcommand name");
111     return;
112   }
113   
114   SGPropertyNode_ptr arg(new SGPropertyNode);
115   JSON::addChildrenToProp( json, arg );
116   
117   globals->get_commands()->execute(name->valuestring, arg);
118 }
119   
120 PropertyChangeWebsocket::PropertyChangeWebsocket(PropertyChangeObserver * propertyChangeObserver)
121     : id(++nextid), _propertyChangeObserver(propertyChangeObserver)
122 {
123 }
124
125 PropertyChangeWebsocket::~PropertyChangeWebsocket()
126 {
127 }
128
129 void PropertyChangeWebsocket::close()
130 {
131   SG_LOG(SG_NETWORK, SG_INFO, "closing PropertyChangeWebsocket #" << id);
132   _watchedNodes.clear();
133 }
134
135 void PropertyChangeWebsocket::handleGetCommand(const string_list& nodes, WebsocketWriter &writer)
136 {
137   double t = fgGetDouble("/sim/time/elapsed-sec");
138   string_list::const_iterator it;
139   for (it = nodes.begin(); it != nodes.end(); ++it) {
140     SGPropertyNode_ptr n = fgGetNode(*it);
141     if (!n) {
142       SG_LOG(SG_NETWORK, SG_WARN, "httpd: get '" << *it << "'  not found");
143       return;
144     }
145     
146     writer.writeText( JSON::toJsonString( false, n, 0, t ) );
147   } // of nodes iteration
148 }
149   
150 void PropertyChangeWebsocket::handleRequest(const HTTPRequest & request, WebsocketWriter &writer)
151 {
152   if (request.Content.empty()) return;
153
154   /*
155    * allowed JSON is
156    {
157    command : 'addListener',
158    nodes : [
159    '/bar/baz',
160    '/foo/bar'
161    ],
162    node: '/bax/foo'
163    }
164    */
165   cJSON * json = cJSON_Parse(request.Content.c_str());
166   if ( NULL != json) {
167     string command;
168     cJSON * j = cJSON_GetObjectItem(json, "command");
169     if ( NULL != j && NULL != j->valuestring) {
170       command = j->valuestring;
171     }
172
173     // handle a single node name, or an array of them
174     string_list nodeNames;
175     j = cJSON_GetObjectItem(json, "node");
176     if ( NULL != j && NULL != j->valuestring) {
177         nodeNames.push_back(simgear::strutils::strip(string(j->valuestring)));
178     }
179
180     cJSON * nodes = cJSON_GetObjectItem(json, "nodes");
181     if ( NULL != nodes) {
182       for (int i = 0; i < cJSON_GetArraySize(nodes); i++) {
183         cJSON * node = cJSON_GetArrayItem(nodes, i);
184         if ( NULL == node) continue;
185         if ( NULL == node->valuestring) continue;
186         nodeNames.push_back(simgear::strutils::strip(string(node->valuestring)));
187       }
188     }
189     
190     if (command == "get") {
191       handleGetCommand(nodeNames, writer);
192     } else if (command == "set") {
193       handleSetCommand(nodeNames, json, writer);
194     } else if (command == "exec") {
195       handleExecCommand(json);
196     } else {
197       string_list::const_iterator it;
198       for (it = nodeNames.begin(); it != nodeNames.end(); ++it) {
199         _watchedNodes.handleCommand(command, *it, _propertyChangeObserver);
200       }
201     }
202     
203     cJSON_Delete(json);
204   }
205 }
206
207 void PropertyChangeWebsocket::poll(WebsocketWriter & writer)
208 {
209   for (WatchedNodesList::iterator it = _watchedNodes.begin(); it != _watchedNodes.end(); ++it) {
210     SGPropertyNode_ptr node = *it;
211
212     string newValue;
213     if (_propertyChangeObserver->isChangedValue(node)) {
214       string out = JSON::toJsonString( false, node, 0, fgGetDouble("/sim/time/elapsed-sec") );
215       SG_LOG(SG_NETWORK, SG_DEBUG, "PropertyChangeWebsocket::poll() new Value for " << node->getPath(true) << " '" << node->getStringValue() << "' #" << id << ": " << out );
216       writer.writeText( out );
217     }
218   }
219 }
220
221 void PropertyChangeWebsocket::WatchedNodesList::handleCommand(const string & command, const string & node,
222     PropertyChangeObserver * propertyChangeObserver)
223 {
224   if (command == "addListener") {
225     for (iterator it = begin(); it != end(); ++it) {
226       if (node == (*it)->getPath(true)) {
227         SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (duplicate)");
228         return; // dupliate
229       }
230     }
231     SGPropertyNode_ptr n = propertyChangeObserver->addObservation(node);
232     if (n.valid()) push_back(n);
233     SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
234
235   } else if (command == "removeListener") {
236     for (iterator it = begin(); it != end(); ++it) {
237       if (node == (*it)->getPath(true)) {
238         this->erase(it);
239         SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
240         return;
241       }
242     }
243     SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (not found)");
244   }
245 }
246
247 } // namespace http
248 } // namespace flightgear