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

Last change on this file since 14418 was 14351, checked in by tbretz, 12 years ago
The state from the callback was not yet properly propagated. Implemented kSM_KeepState to signal that the state should not change.
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 int 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 return StateMachineImp::kSM_KeepState;
265 }
266
267 void *JsSubscribe(const std::string &service)
268 {
269 if (!fImp)
270 return 0;
271
272 // Do not subscribe twice
273 if (fEvents.find(service)!=fEvents.end())
274 return 0;
275
276 return fEvents[service] = &fImp->Subscribe(service)(fImp->Wrap(bind(&RemoteControl<T>::Handle, this, placeholders::_1, service)));
277 }
278
279 bool JsUnsubscribe(const std::string &service)
280 {
281 if (!fImp)
282 return false;
283
284 const auto it = fEvents.find(service);
285 if (it==fEvents.end())
286 return false;
287
288 fImp->Unsubscribe(it->second);
289 fEvents.erase(it);
290
291 return true;
292 }
293
294 void UnsubscribeAll()
295 {
296 for (auto it=fEvents.begin(); it!=fEvents.end(); it++)
297 fImp->Unsubscribe(it->second);
298 fEvents.clear();
299 }
300
301 // ===========================================================================
302
303
304public:
305 // Redirect asynchronous output to the output window
306 RemoteControl(const char *name) : T(name),
307 RemoteControlImp(T::GetStreamOut(), T::GetStreamIn()), fImp(0)
308 {
309 }
310
311 bool PrintGeneralHelp()
312 {
313 T::PrintGeneralHelp();
314 lout << " " << kUnderline << "Specific commands:\n";
315 lout << kBold << " h,help <arg> " << kReset << "List help text for given server or command.\n";
316 lout << kBold << " svc,services " << kReset << "List all services in the network.\n";
317 lout << kBold << " st,states " << kReset << "List all states in the network.\n";
318 lout << kBold << " > <text> " << kReset << "Echo <text> to the output stream\n";
319 lout << kBold << " .s " << kReset << "Wait for the state-machine to change to the given state.\n";
320 lout << " " " .s <server> [<state> [<timeout> [<label>]]]\n";
321 lout << " " "<server> The server for which state to wait (e.g. FTM_CONTROL)\n";
322 lout << " " "<state> The state id (see 'states') for which to wait (e.g. 3)\n";
323 lout << " " "<imeout> A timeout in millisenconds how long to wait (e.g. 500)\n";
324 lout << " " "<label> A label (number) until which everything is skipped in case of timeout\n";
325 lout << endl;
326 return true;
327 }
328
329 bool PrintCommands()
330 {
331 lout << endl << kBold << "List of commands:" << endl;
332 PrintDescription(lout, true);
333 return true;
334 }
335
336 // returns whether a command should be put into the history
337 bool Process(const std::string &str)
338 {
339 if (str.substr(0, 2)=="h " || str.substr(0, 5)=="help ")
340 {
341 const size_t p1 = str.find_first_of(' ');
342 const string svc = str.substr(p1+1);
343
344 const size_t p3 = svc.find_first_of('/');
345 const string s = svc.substr(0, p3);
346 const string c = p3==string::npos?"":svc.substr(p3+1);
347
348 lout << endl;
349 if (!fCurrentServer.empty())
350 {
351 if (PrintDescription(lout, true, fCurrentServer, svc)==0)
352 lout << " " << svc << ": <not found>" << endl;
353 }
354 else
355 {
356 if (PrintDescription(lout, true, s, c)==0)
357 lout << " <no matches found>" <<endl;
358 }
359
360 return true;
361 }
362
363 if (str.substr(0, 4)==".js ")
364 {
365 string opt(str.substr(4));
366
367 map<string,string> data = Tools::Split(opt, true);
368 if (opt.size()==0)
369 {
370 if (data.size()==0)
371 lout << kRed << "JavaScript filename missing." << endl;
372 else
373 lout << kRed << "Equal sign missing in argument '" << data.begin()->first << "'" << endl;
374
375 return true;
376 }
377
378 T::fScript = opt;
379 JsRun(opt, data);
380 return true;
381 }
382
383 if (str.substr(0, 3)==".s ")
384 {
385 istringstream in(str.substr(3));
386
387 int state=-100, ms=0;
388 string server;
389
390 in >> server >> state >> ms;
391 if (state==-100)
392 {
393 lout << kRed << "Couldn't parse state id in '" << str.substr(3) << "'" << endl;
394 return true;
395 }
396
397 const int rc = JsWait(server, state, ms);
398
399 if (rc<0 || rc==1)
400 return true;
401
402 int label = -1;
403 in >> label;
404 if (in.fail() && !in.eof())
405 {
406 lout << kRed << "Invalid label in '" << str.substr(3) << "'" << endl;
407 T::StopScript();
408 return true;
409 }
410 T::SetLabel(label);
411
412 return true;
413 }
414
415 if (str[0]=='>')
416 {
417 fImp->Comment(Tools::Trim(str.substr(1)));
418 return true;
419 }
420
421 if (ReadlineColor::Process(lout, str))
422 return true;
423
424 if (T::Process(str))
425 return true;
426
427 if (str=="services" || str=="svc")
428 {
429 PrintDescription(lout, false);
430 return true;
431 }
432
433 if (str=="states" || str=="st")
434 {
435 PrintStates(lout);
436 return true;
437 }
438
439 return !ProcessCommand(str);
440 }
441
442 void SetReceiver(StateMachineDimControl &imp) { fImp = &imp; }
443};
444
445
446
447// **************************************************************************
448/** @class RemoteStream
449
450 */
451// **************************************************************************
452#include "Console.h"
453
454class RemoteStream : public RemoteControl<ConsoleStream>
455{
456public:
457 RemoteStream(const char *name, bool null = false)
458 : RemoteControl<ConsoleStream>(name) { SetNullOutput(null); }
459};
460
461// **************************************************************************
462/** @class RemoteConsole
463
464@brief Derives the RemoteControl from Control and adds a proper prompt
465
466This is basically a RemoteControl, which derives through the template
467argument from the Console class. It enhances the functionality of
468the remote control with a proper updated prompt.
469
470 */
471// **************************************************************************
472
473class RemoteConsole : public RemoteControl<Console>
474{
475public:
476 RemoteConsole(const char *name, bool continous=false) :
477 RemoteControl<Console>(name)
478 {
479 SetContinous(continous);
480 }
481 string GetUpdatePrompt() const;
482};
483
484// **************************************************************************
485/** @class RemoteShell
486
487@brief Derives the RemoteControl from Shell and adds colored prompt
488
489This is basically a RemoteControl, which derives through the template
490argument from the Shell class. It enhances the functionality of
491the local control with a proper updated prompt.
492
493 */
494// **************************************************************************
495#include "Shell.h"
496
497class RemoteShell : public RemoteControl<Shell>
498{
499public:
500 RemoteShell(const char *name, bool = false) :
501 RemoteControl<Shell>(name)
502 {
503 }
504 string GetUpdatePrompt() const;
505};
506
507#endif
Note: See TracBrowser for help on using the repository browser.