source: trunk/FACT++/src/RemoteControl.h@ 14303

Last change on this file since 14303 was 14135, checked in by tbretz, 12 years ago
Added a function dim.out which will only print to the local console; improved conversion from dim service elements to ECMA numbers
File size: 15.9 KB
Line 
1#ifndef FACT_RemoteControl
2#define FACT_RemoteControl
3
4// **************************************************************************
5/** @class RemoteControlImp
6
7@brief This implements the basic functions of a remote control via dim
8
9Through a ServiceList object this object subscribes to all available
10SERVICE_LISTs in the dim network. This allows to keep an up-to-date
11list of all servers and services. Its ProcessCommand member function
12allows to emit commands according to the services found in the network.
13Its infoHandler() is called as an update notifier from the ClientList
14object.
15
16**/
17// **************************************************************************
18#include <string>
19
20using namespace std;
21
22class RemoteControlImp
23{
24protected:
25 std::ostream &lout; /// Output stream for local synchrounous output
26
27 std::string fCurrentServer; /// The server to which we currently cd'ed
28
29protected:
30 // Redirect asynchronous output to the output window
31 RemoteControlImp(std::ostream &, std::ostream &in) : lout(in)
32 {
33 }
34 virtual ~RemoteControlImp() { }
35 bool ProcessCommand(const std::string &str);
36
37 virtual bool HasServer(const std::string &) { return false; }
38 virtual bool SendDimCommand(ostream &, std::string &, const std::string &) { return false; }
39};
40
41// **************************************************************************
42/** @class RemoteControl
43
44@brief Implements a remote control based on a Readline class for the dim network
45
46This template implements all functions which overwrite any function from the
47Readline class. Since several derivatives of the Readline class implement
48different kind of Readline access, this class can be derived by any of them
49due to its template argument. However, the normal case will be
50deriving it from either Console or Shell.
51
52@tparam T
53 The base class for RemoteControl. Either Readlien or a class
54 deriving from it. This is usually either Console or Shell.
55
56**/
57// **************************************************************************
58#include "StateMachineDimControl.h"
59
60#include "InterpreterV8.h"
61#include "ReadlineColor.h"
62#include "Event.h"
63#include "tools.h"
64
65template <class T>
66class RemoteControl : public T, public RemoteControlImp, public InterpreterV8
67{
68protected:
69 StateMachineDimControl *fImp;
70
71 void SetSection(int s) { if (fImp) fImp->Write(Time(), "", s); }
72
73 int Write(const Time &time, const std::string &txt, int qos=MessageImp::kMessage)
74 {
75 if (!fImp)
76 return 0;
77 return fImp ? fImp->Write(time, txt, qos) : MessageImp::Write(time, txt, qos);
78 }
79
80 void exitHandler(int code) { if (dynamic_cast<MainImp*>(fImp)) dynamic_cast<MainImp*>(fImp)->Stop(code); else exit(code); }
81
82 // ==================== Readline tab-completion =====================
83
84 static void append(std::string &str)
85 {
86 str.append("/");
87 }
88 static void chop(std::string &str)
89 {
90 const size_t p = str.find_first_of('/');
91 if (p!=string::npos)
92 str = str.substr(p+1);
93 }
94
95 // This funtion defines which generator should be called.
96 // If it returns 0 the standard reaqdline generator are called.
97 // Otherwise set the right generator with rl_completion_matches.
98 char **Completion(const char *text, int start, int)
99 {
100 // Get the whole buffer before the tab-position
101 const string b = string(T::GetBuffer());
102 const string s = b.substr(0, start);
103 const string l = Tools::Trim(s.c_str());
104 if (l.empty())
105 {
106 if (fCurrentServer.empty())
107 {
108 const size_t p1 = b.find_first_of(' ');
109 const size_t p2 = b.find_first_of('/');
110
111 if (p1==string::npos && p2!=string::npos)
112 return T::Complete(GetCommandList(), text);
113
114 std::vector<std::string> v = GetServerList();
115 for_each(v.begin(), v.end(), RemoteControl::append);
116 return T::Complete(v, text);
117 }
118 else
119 {
120 std::vector<std::string> v = GetCommandList(fCurrentServer);
121 for_each(v.begin(), v.end(), RemoteControl::chop);
122 return T::Complete(v, text);
123 }
124 }
125 return T::Complete(GetCommandList(l), text);
126 }
127
128 void EventHook()
129 {
130 if (fImp && !fImp->HasServer(fCurrentServer))
131 fCurrentServer = "";
132
133 T::EventHook();
134 }
135
136 // ===== Interface to access the DIM network through the StateMachine ====
137
138 bool HasServer(const std::string &server) { return fImp ? fImp->HasServer(server) : false; }
139 vector<string> GetServerList() const { return fImp ? fImp->GetServerList() : vector<string>(); }
140 vector<string> GetCommandList(const string &server) const { return fImp ? fImp->GetCommandList(server) : vector<string>(); }
141 vector<string> GetCommandList() const { return fImp ? fImp->GetCommandList() : vector<string>(); }
142 int PrintStates(std::ostream &out, const std::string &serv="") const { return fImp ? fImp->PrintStates(out, serv) : 0; }
143 int PrintDescription(std::ostream &out, bool iscmd, const std::string &serv="", const std::string &service="") const
144 { return fImp ? fImp->PrintDescription(out, iscmd, serv, service) : 0; }
145 bool SendDimCommand(ostream &out, std::string &server, const std::string &str)
146 {
147 try
148 {
149 if (fImp)
150 fImp->SendDimCommand(server, str, out);
151 //lout << kGreen << "Command emitted successfully to " << server << "." << endl;
152 return true;
153 }
154 catch (const runtime_error &e)
155 {
156 lout << kRed << e.what() << endl;
157 return false;
158 }
159 }
160
161 // ============ Pseudo-callback interface for the JavaScrip engine =======
162
163 virtual void JsLoad(const std::string &) { SetSection(-2); }
164 virtual void JsStart(const std::string &) { SetSection(-1); }
165 virtual void JsEnd(const std::string &) { UnsubscribeAll(); SetSection(-3); }
166 virtual bool JsSend(const std::string &str) { return ProcessCommand(str); }
167 virtual void JsOut(const std::string &msg) { lout << msg << endl; }
168 virtual void JsPrint(const std::string &msg) { if (fImp) fImp->Comment(msg.empty()?" ":msg); }
169 virtual void JsException(const std::string &str) { if (fImp) fImp->Error(str.empty()?" ":str); }
170
171 pair<int32_t, string> JsState(const std::string &server)
172 {
173 return fImp ? fImp->GetServerState(server) : make_pair(-256, string());
174 }
175
176 void JsSleep(uint32_t ms)
177 {
178 const Time timeout = Time()+boost::posix_time::millisec(ms==0?1:ms);
179
180 T::Lock();
181
182 while (timeout>Time() && !T::IsScriptStopped())
183 usleep(1);
184
185 T::Unlock();
186 }
187
188 int JsWait(const string &server, int32_t state, uint32_t ms)
189 {
190 if (!fImp)
191 {
192 lout << kRed << "RemoteControl class not fully initialized." << endl;
193 T::StopScript();
194 return -1;
195 }
196
197 if (!HasServer(server))
198 {
199 lout << kRed << "Server '" << server << "' not found." << endl;
200 T::StopScript();
201 return -1;
202 }
203
204 T::Lock();
205
206 const Time timeout = ms<=0 ? Time(Time::none) : Time()+boost::posix_time::millisec(ms);
207
208 int rc = 0;
209 while (timeout>Time() && !T::IsScriptStopped())
210 {
211 const pair<int32_t, string> st = fImp->GetServerState(server);
212 if (st.first==-256)
213 {
214 lout << kRed << "Server '" << server << "' disconnected." << endl;
215 T::StopScript();
216 return -1;
217 }
218 if (st.first==state)
219 {
220 rc = 1;
221 break;
222 }
223
224 usleep(1);
225 }
226 T::Unlock();
227
228 return rc;
229 }
230
231 vector<Description> JsDescription(const string &service)
232 {
233 return fImp ? fImp->GetDescription(service) : vector<Description>();
234 }
235
236 // Keep a copy of the data for access by V8
237 map<string, EventImp *> fEvents; // For unsibscription
238 map<string, pair<uint64_t, Event>> fData;
239
240 std::mutex fMutex;
241
242 pair<uint64_t, EventImp *> JsGetEvent(const std::string &service)
243 {
244 const lock_guard<mutex> lock(fMutex);
245
246 const auto it = fData.find(service);
247
248 return it==fData.end() ? make_pair(uint64_t(0), (Event*)0) : make_pair(it->second.first, &it->second.second);
249 }
250
251 void Handle(const EventImp &evt, const string &service)
252 {
253 const lock_guard<mutex> lock(fMutex);
254
255 const auto it = fData.find(service);
256 if (it==fData.end())
257 fData[service] = make_pair(0, static_cast<Event>(evt));
258 else
259 {
260 it->second.first++;
261 it->second.second = static_cast<Event>(evt);
262 }
263 }
264
265 void *JsSubscribe(const std::string &service)
266 {
267 if (!fImp)
268 return 0;
269
270 // Do not subscribe twice
271 if (fEvents.find(service)!=fEvents.end())
272 return 0;
273
274 return fEvents[service] = &fImp->Subscribe(service)(fImp->Wrap(bind(&RemoteControl<T>::Handle, this, placeholders::_1, service)));
275 }
276
277 bool JsUnsubscribe(const std::string &service)
278 {
279 if (!fImp)
280 return false;
281
282 const auto it = fEvents.find(service);
283 if (it==fEvents.end())
284 return false;
285
286 fImp->Unsubscribe(it->second);
287 fEvents.erase(it);
288
289 return true;
290 }
291
292 void UnsubscribeAll()
293 {
294 for (auto it=fEvents.begin(); it!=fEvents.end(); it++)
295 fImp->Unsubscribe(it->second);
296 fEvents.clear();
297 }
298
299 // ===========================================================================
300
301
302public:
303 // Redirect asynchronous output to the output window
304 RemoteControl(const char *name) : T(name),
305 RemoteControlImp(T::GetStreamOut(), T::GetStreamIn()), fImp(0)
306 {
307 }
308
309 bool PrintGeneralHelp()
310 {
311 T::PrintGeneralHelp();
312 lout << " " << kUnderline << "Specific commands:\n";
313 lout << kBold << " h,help <arg> " << kReset << "List help text for given server or command.\n";
314 lout << kBold << " svc,services " << kReset << "List all services in the network.\n";
315 lout << kBold << " st,states " << kReset << "List all states in the network.\n";
316 lout << kBold << " > <text> " << kReset << "Echo <text> to the output stream\n";
317 lout << kBold << " .s " << kReset << "Wait for the state-machine to change to the given state.\n";
318 lout << " " " .s <server> [<state> [<timeout> [<label>]]]\n";
319 lout << " " "<server> The server for which state to wait (e.g. FTM_CONTROL)\n";
320 lout << " " "<state> The state id (see 'states') for which to wait (e.g. 3)\n";
321 lout << " " "<imeout> A timeout in millisenconds how long to wait (e.g. 500)\n";
322 lout << " " "<label> A label (number) until which everything is skipped in case of timeout\n";
323 lout << endl;
324 return true;
325 }
326
327 bool PrintCommands()
328 {
329 lout << endl << kBold << "List of commands:" << endl;
330 PrintDescription(lout, true);
331 return true;
332 }
333
334 // returns whether a command should be put into the history
335 bool Process(const std::string &str)
336 {
337 if (str.substr(0, 2)=="h " || str.substr(0, 5)=="help ")
338 {
339 const size_t p1 = str.find_first_of(' ');
340 const string svc = str.substr(p1+1);
341
342 const size_t p3 = svc.find_first_of('/');
343 const string s = svc.substr(0, p3);
344 const string c = p3==string::npos?"":svc.substr(p3+1);
345
346 lout << endl;
347 if (!fCurrentServer.empty())
348 {
349 if (PrintDescription(lout, true, fCurrentServer, svc)==0)
350 lout << " " << svc << ": <not found>" << endl;
351 }
352 else
353 {
354 if (PrintDescription(lout, true, s, c)==0)
355 lout << " <no matches found>" <<endl;
356 }
357
358 return true;
359 }
360
361 if (str.substr(0, 4)==".js ")
362 {
363 string opt(str.substr(4));
364
365 map<string,string> data = Tools::Split(opt, true);
366 if (opt.size()==0)
367 {
368 if (data.size()==0)
369 lout << kRed << "JavaScript filename missing." << endl;
370 else
371 lout << kRed << "Equal sign missing in argument '" << data.begin()->first << "'" << endl;
372
373 return true;
374 }
375
376 T::fScript = opt;
377 JsRun(opt, data);
378 return true;
379 }
380
381 if (str.substr(0, 3)==".s ")
382 {
383 istringstream in(str.substr(3));
384
385 int state=-100, ms=0;
386 string server;
387
388 in >> server >> state >> ms;
389 if (state==-100)
390 {
391 lout << kRed << "Couldn't parse state id in '" << str.substr(3) << "'" << endl;
392 return true;
393 }
394
395 const int rc = JsWait(server, state, ms);
396
397 if (rc<0 || rc==1)
398 return true;
399
400 int label = -1;
401 in >> label;
402 if (in.fail() && !in.eof())
403 {
404 lout << kRed << "Invalid label in '" << str.substr(3) << "'" << endl;
405 T::StopScript();
406 return true;
407 }
408 T::SetLabel(label);
409
410 return true;
411 }
412
413 if (str[0]=='>')
414 {
415 fImp->Comment(Tools::Trim(str.substr(1)));
416 return true;
417 }
418
419 if (ReadlineColor::Process(lout, str))
420 return true;
421
422 if (T::Process(str))
423 return true;
424
425 if (str=="services" || str=="svc")
426 {
427 PrintDescription(lout, false);
428 return true;
429 }
430
431 if (str=="states" || str=="st")
432 {
433 PrintStates(lout);
434 return true;
435 }
436
437 return !ProcessCommand(str);
438 }
439
440 void SetReceiver(StateMachineDimControl &imp) { fImp = &imp; }
441};
442
443
444
445// **************************************************************************
446/** @class RemoteStream
447
448 */
449// **************************************************************************
450#include "Console.h"
451
452class RemoteStream : public RemoteControl<ConsoleStream>
453{
454public:
455 RemoteStream(const char *name, bool null = false)
456 : RemoteControl<ConsoleStream>(name) { SetNullOutput(null); }
457};
458
459// **************************************************************************
460/** @class RemoteConsole
461
462@brief Derives the RemoteControl from Control and adds a proper prompt
463
464This is basically a RemoteControl, which derives through the template
465argument from the Console class. It enhances the functionality of
466the remote control with a proper updated prompt.
467
468 */
469// **************************************************************************
470
471class RemoteConsole : public RemoteControl<Console>
472{
473public:
474 RemoteConsole(const char *name, bool continous=false) :
475 RemoteControl<Console>(name)
476 {
477 SetContinous(continous);
478 }
479 string GetUpdatePrompt() const;
480};
481
482// **************************************************************************
483/** @class RemoteShell
484
485@brief Derives the RemoteControl from Shell and adds colored prompt
486
487This is basically a RemoteControl, which derives through the template
488argument from the Shell class. It enhances the functionality of
489the local control with a proper updated prompt.
490
491 */
492// **************************************************************************
493#include "Shell.h"
494
495class RemoteShell : public RemoteControl<Shell>
496{
497public:
498 RemoteShell(const char *name, bool = false) :
499 RemoteControl<Shell>(name)
500 {
501 }
502 string GetUpdatePrompt() const;
503};
504
505#endif
Note: See TracBrowser for help on using the repository browser.