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

Last change on this file since 14550 was 14546, checked in by tbretz, 12 years ago
Some simplification to the JavaScript interfacing, mainly unifying the different maps, added a callback in case when an event is received.
File size: 16.7 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 JsAlarm(const std::string &msg) { if (fImp) fImp->Alarm(msg.empty()?" ":msg); }
170 virtual void JsException(const std::string &str) { if (fImp) fImp->Error(str.empty()?" ":str); }
171
172 State JsState(const std::string &server)
173 {
174 return fImp ? fImp->GetServerState(server) : State(-256, string());
175 }
176
177 /*
178 void JsSleep(uint32_t ms)
179 {
180 const Time timeout = Time()+boost::posix_time::millisec(ms==0?1:ms);
181
182 T::Lock();
183
184 while (timeout>Time() && !T::IsScriptStopped())
185 usleep(1);
186
187 T::Unlock();
188 }*/
189
190 int JsWait(const string &server, int32_t state, uint32_t ms)
191 {
192 if (!fImp)
193 {
194 lout << kRed << "RemoteControl class not fully initialized." << endl;
195 T::StopScript();
196 return -1;
197 }
198
199 if (!HasServer(server))
200 {
201 lout << kRed << "Server '" << server << "' not found." << endl;
202 T::StopScript();
203 return -1;
204 }
205
206 T::Lock();
207
208 const Time timeout = ms<=0 ? Time(Time::none) : Time()+boost::posix_time::millisec(ms);
209
210 int rc = 0;
211 do
212 {
213 State st = fImp->GetServerState(server);
214 if (st.index==-256)
215 {
216 lout << kRed << "Server '" << server << "' disconnected." << endl;
217 T::StopScript();
218 return -1;
219 }
220 if (st.index==state)
221 {
222 rc = 1;
223 break;
224 }
225
226 usleep(1);
227 }
228 while (timeout>Time() && !T::IsScriptStopped());
229
230 T::Unlock();
231
232 return rc;
233 }
234
235 vector<Description> JsDescription(const string &service)
236 {
237 return fImp ? fImp->GetDescription(service) : vector<Description>();
238 }
239
240 struct EventInfo
241 {
242 EventImp *ptr;
243 uint64_t counter;
244 Event data;
245 EventInfo(EventImp *p) : ptr(p), counter(0) { }
246 };
247
248 // Keep a copy of the data for access by V8
249 map<string, EventInfo> fInfo;
250 std::mutex fMutex;
251
252 pair<uint64_t, EventImp *> JsGetEvent(const std::string &service)
253 {
254 // This function is called from JavaScript
255 const lock_guard<mutex> lock(fMutex);
256
257 const auto it = fInfo.find(service);
258 if (it==fInfo.end())
259 return make_pair(0, static_cast<EventImp*>(NULL));
260
261 EventInfo &info = it->second;
262 return make_pair(info.counter, (EventImp*)&info.data);
263 }
264
265 int Handle(const EventImp &evt, const string &service)
266 {
267 // This function is called from the StateMachine
268 fMutex.lock();
269
270 const auto it = fInfo.find(service);
271
272 // This should never happen... but just in case.
273 if (it==fInfo.end())
274 {
275 fMutex.unlock();
276 return StateMachineImp::kSM_KeepState;
277 }
278
279 EventInfo &info = it->second;
280
281 const uint64_t cnt = info.counter++;
282 info.data = static_cast<Event>(evt);
283
284 fMutex.unlock();
285
286 JsHandleEvent(evt, cnt, service);
287
288 return StateMachineImp::kSM_KeepState;
289 }
290
291 void *JsSubscribe(const std::string &service)
292 {
293 if (!fImp)
294 return 0;
295
296 // This function is called from JavaScript
297 const lock_guard<mutex> lock(fMutex);
298
299 // Do not subscribe twice
300 if (fInfo.find(service)!=fInfo.end())
301 return 0;
302
303 EventImp *ptr = &fImp->Subscribe(service)(fImp->Wrap(bind(&RemoteControl<T>::Handle, this, placeholders::_1, service)));
304 fInfo.insert(make_pair(service, EventInfo(ptr)));
305 return ptr;
306 }
307
308 bool JsUnsubscribe(const std::string &service)
309 {
310 if (!fImp)
311 return false;
312
313 // This function is called from JavaScript
314 const lock_guard<mutex> lock(fMutex);
315
316 const auto it = fInfo.find(service);
317 if (it==fInfo.end())
318 return false;
319
320 fImp->Unsubscribe(it->second.ptr);
321 fInfo.erase(it);
322
323 return true;
324 }
325
326 void UnsubscribeAll()
327 {
328 // This function is called from JavaScript
329 const lock_guard<mutex> lock(fMutex);
330
331 for (auto it=fInfo.begin(); it!=fInfo.end(); it++)
332 fImp->Unsubscribe(it->second.ptr);
333
334 fInfo.clear();
335 }
336
337 // ===========================================================================
338
339
340public:
341 // Redirect asynchronous output to the output window
342 RemoteControl(const char *name) : T(name),
343 RemoteControlImp(T::GetStreamOut(), T::GetStreamIn()), fImp(0)
344 {
345 }
346
347 bool PrintGeneralHelp()
348 {
349 T::PrintGeneralHelp();
350 lout << " " << kUnderline << "Specific commands:\n";
351 lout << kBold << " h,help <arg> " << kReset << "List help text for given server or command.\n";
352 lout << kBold << " svc,services " << kReset << "List all services in the network.\n";
353 lout << kBold << " st,states " << kReset << "List all states in the network.\n";
354 lout << kBold << " > <text> " << kReset << "Echo <text> to the output stream\n";
355 lout << kBold << " .s " << kReset << "Wait for the state-machine to change to the given state.\n";
356 lout << " " " .s <server> [<state> [<timeout> [<label>]]]\n";
357 lout << " " "<server> The server for which state to wait (e.g. FTM_CONTROL)\n";
358 lout << " " "<state> The state id (see 'states') for which to wait (e.g. 3)\n";
359 lout << " " "<imeout> A timeout in millisenconds how long to wait (e.g. 500)\n";
360 lout << " " "<label> A label (number) until which everything is skipped in case of timeout\n";
361 lout << endl;
362 return true;
363 }
364
365 bool PrintCommands()
366 {
367 lout << endl << kBold << "List of commands:" << endl;
368 PrintDescription(lout, true);
369 return true;
370 }
371
372 // returns whether a command should be put into the history
373 bool Process(const std::string &str)
374 {
375 if (str.substr(0, 2)=="h " || str.substr(0, 5)=="help ")
376 {
377 const size_t p1 = str.find_first_of(' ');
378 const string svc = str.substr(p1+1);
379
380 const size_t p3 = svc.find_first_of('/');
381 const string s = svc.substr(0, p3);
382 const string c = p3==string::npos?"":svc.substr(p3+1);
383
384 lout << endl;
385 if (!fCurrentServer.empty())
386 {
387 if (PrintDescription(lout, true, fCurrentServer, svc)==0)
388 lout << " " << svc << ": <not found>" << endl;
389 }
390 else
391 {
392 if (PrintDescription(lout, true, s, c)==0)
393 lout << " <no matches found>" <<endl;
394 }
395
396 return true;
397 }
398
399 if (str.substr(0, 4)==".js ")
400 {
401 string opt(str.substr(4));
402
403 map<string,string> data = Tools::Split(opt, true);
404 if (opt.size()==0)
405 {
406 if (data.size()==0)
407 lout << kRed << "JavaScript filename missing." << endl;
408 else
409 lout << kRed << "Equal sign missing in argument '" << data.begin()->first << "'" << endl;
410
411 return true;
412 }
413
414 T::fScript = opt;
415 JsRun(opt, data);
416 return true;
417 }
418
419 if (str.substr(0, 3)==".s ")
420 {
421 istringstream in(str.substr(3));
422
423 int state=-100, ms=0;
424 string server;
425
426 in >> server >> state >> ms;
427 if (state==-100)
428 {
429 lout << kRed << "Couldn't parse state id in '" << str.substr(3) << "'" << endl;
430 return true;
431 }
432
433 const int rc = JsWait(server, state, ms);
434
435 if (rc<0 || rc==1)
436 return true;
437
438 int label = -1;
439 in >> label;
440 if (in.fail() && !in.eof())
441 {
442 lout << kRed << "Invalid label in '" << str.substr(3) << "'" << endl;
443 T::StopScript();
444 return true;
445 }
446 T::SetLabel(label);
447
448 return true;
449 }
450
451 if (str[0]=='>')
452 {
453 fImp->Comment(Tools::Trim(str.substr(1)));
454 return true;
455 }
456
457 if (ReadlineColor::Process(lout, str))
458 return true;
459
460 if (T::Process(str))
461 return true;
462
463 if (str=="services" || str=="svc")
464 {
465 PrintDescription(lout, false);
466 return true;
467 }
468
469 if (str=="states" || str=="st")
470 {
471 PrintStates(lout);
472 return true;
473 }
474
475 return !ProcessCommand(str);
476 }
477
478 void SetReceiver(StateMachineDimControl &imp) { fImp = &imp; }
479};
480
481
482
483// **************************************************************************
484/** @class RemoteStream
485
486 */
487// **************************************************************************
488#include "Console.h"
489
490class RemoteStream : public RemoteControl<ConsoleStream>
491{
492public:
493 RemoteStream(const char *name, bool null = false)
494 : RemoteControl<ConsoleStream>(name) { SetNullOutput(null); }
495};
496
497// **************************************************************************
498/** @class RemoteConsole
499
500@brief Derives the RemoteControl from Control and adds a proper prompt
501
502This is basically a RemoteControl, which derives through the template
503argument from the Console class. It enhances the functionality of
504the remote control with a proper updated prompt.
505
506 */
507// **************************************************************************
508
509class RemoteConsole : public RemoteControl<Console>
510{
511public:
512 RemoteConsole(const char *name, bool continous=false) :
513 RemoteControl<Console>(name)
514 {
515 SetContinous(continous);
516 }
517 string GetUpdatePrompt() const;
518};
519
520// **************************************************************************
521/** @class RemoteShell
522
523@brief Derives the RemoteControl from Shell and adds colored prompt
524
525This is basically a RemoteControl, which derives through the template
526argument from the Shell class. It enhances the functionality of
527the local control with a proper updated prompt.
528
529 */
530// **************************************************************************
531#include "Shell.h"
532
533class RemoteShell : public RemoteControl<Shell>
534{
535public:
536 RemoteShell(const char *name, bool = false) :
537 RemoteControl<Shell>(name)
538 {
539 }
540 string GetUpdatePrompt() const;
541};
542
543#endif
Note: See TracBrowser for help on using the repository browser.