ARB
trackers.cxx
Go to the documentation of this file.
1 // ============================================================= //
2 // //
3 // File : trackers.cxx //
4 // Purpose : //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in March 2013 //
7 // Institute of Microbiology (Technical University Munich) //
8 // http://www.arb-home.de/ //
9 // //
10 // ============================================================= //
11 
12 #include "trackers.hxx"
13 #include "macros.hxx"
14 #include "recmac.hxx"
15 #include "dbserver.hxx"
16 
17 #include <aw_msg.hxx>
18 #include <arb_strarray.h>
19 #include <arb_sleep.h>
20 #include <ad_remote.h>
21 #include <unistd.h>
22 #include <ad_cb.h>
23 
24 bool BoundActionTracker::reconfigure(const char *application_id, GBDATA *IF_ASSERTION_USED(gb_main)) {
25  ma_assert(gb_main == gbmain);
26  ma_assert(strcmp(id, "ARB_IMPORT") == 0); // currently only ARB_IMPORT-tracker gets reconfigured
27  freedup(id, application_id);
28  return true;
29 }
30 
31 void BoundActionTracker::set_recording(bool recording) {
33  {
36  GBDATA *gb_recAuth = GB_searchOrCreate_int(get_gbmain(), remote.recAuth(), 0);
37 
38  if (!gb_recAuth) {
39  error = GB_await_error();
40  }
41  else {
42  pid_t pid = getpid();
43  pid_t recPid = GB_read_int(gb_recAuth);
44 
45  if (recording) {
46  if (recPid == 0) {
47  error = GB_write_int(gb_recAuth, pid); // allocate permission to record
48  }
49  else {
50  error = GBS_global_string("Detected two recording clients with id '%s'", get_application_id());
51  }
52  }
53  else {
54  if (recPid == pid) { // this is the authorized process
55  error = GB_write_int(gb_recAuth, 0); // clear permission
56  }
57  }
58  }
59 
60  if (error) {
61  error = GB_set_macro_error(get_gbmain(), error);
62  if (error) GBK_terminatef("Failed to set macro-error: %s", error);
63  }
64  }
65  set_tracking(recording);
66 }
67 
68 static GB_ERROR announce_recording(GBDATA *gb_main, int record) {
69  GB_transaction ta(gb_main);
70  GBDATA *gb_recording = GB_searchOrCreate_int(gb_main, MACRO_TRIGGER_RECORDING, record);
71  return gb_recording ? GB_write_int(gb_recording, record) : GB_await_error();
72 }
73 
74 GB_ERROR MacroRecorder::start_recording(const char *file, const char *stop_action_name, bool expand_existing) {
76  if (is_tracking()) error = "Already recording macro";
77  else {
78  recording = new RecordingMacro(file, get_application_id(), stop_action_name, expand_existing);
79  set_recording(true);
80 
81  error = recording->has_error();
82  if (!error) error = announce_recording(get_gbmain(), 1);
83  if (error) {
84  GB_ERROR stop_error = stop_recording();
85  if (stop_error) fprintf(stderr, "Error while stopping macro recording: %s\n", stop_error);
86  }
87  }
88  return error;
89 }
90 
93  if (!is_tracking()) {
94  error = "Not recording macro";
95  }
96  else {
97  error = recording->stop();
98 
99  delete recording;
100  recording = NULp;
101  set_recording(false);
102 
103  GB_ERROR ann_error = announce_recording(get_gbmain(), 0);
104  if (error) {
105  if (ann_error) fprintf(stderr, "Error in announce_recording: %s\n", ann_error);
106  }
107  else {
108  error = ann_error;
109  }
110  }
111  return error;
112 }
113 
114 static void getKnownMacroClients(ConstStrArray& clientNames, GBDATA *gb_main) {
115  GB_transaction ta(gb_main);
116 
117  GBDATA *gb_remote = GB_search(gb_main, REMOTE_BASE, GB_FIND);
118  GBDATA *gb_control = GB_search(gb_main, MACRO_TRIGGER_CONTAINER, GB_FIND);
119 
120  clientNames.erase();
121  if (gb_remote) {
122  for (GBDATA *gb_client = GB_child(gb_remote); gb_client; gb_client = GB_nextChild(gb_client)) {
123  if (gb_client != gb_control) {
124  const char *client_id = GB_read_key_pntr(gb_client);
125  clientNames.put(client_id);
126  }
127  }
128  }
129 }
130 __ATTR__USERESULT inline GB_ERROR setIntEntryToZero(GBDATA *gb_main, const char *entryPath) {
131  GBDATA *gbe = GB_search(gb_main, entryPath, GB_INT);
132  return gbe ? GB_write_int(gbe, 0) : GB_await_error();
133 }
134 
136  // clear all granted client authorizations
137  GB_transaction ta(gb_main);
138  GB_ERROR error = NULp;
139 
140  ConstStrArray clientNames;
141  getKnownMacroClients(clientNames, gb_main);
142 
143  for (size_t i = 0; i<clientNames.size() && !error; ++i) {
144  remote_awars remote(clientNames[i]);
145  error = setIntEntryToZero(gb_main, remote.authReq());
146  if (!error) error = setIntEntryToZero(gb_main, remote.authAck());
147  if (!error) error = setIntEntryToZero(gb_main, remote.granted());
148  // if (!error) error = setIntEntryToZero(gb_main, remote.recAuth()); // @@@ clear elsewhere
149  }
150  if (error) error = GBS_global_string("error in clearMacroExecutionAuthorization: %s", error);
151  return error;
152 }
153 
154 class ExecutingMacro : virtual Noncopyable {
155  RootCallback done_cb;
156 
157  ExecutingMacro *next;
158  static ExecutingMacro *head;
159 
160  ExecutingMacro(const RootCallback& execution_done_cb)
161  : done_cb(execution_done_cb),
162  next(head)
163  {
164  head = this;
165  }
166 
167  void call() const { done_cb(AW_root::SINGLETON); }
168  void destroy() { head = next; delete this; }
169 
170 public:
171 
172  static void add(const RootCallback& execution_done_cb) { new ExecutingMacro(execution_done_cb); }
173  static bool done() {
174  // returns true if the last macro (of all recursively called macros) terminates
175  if (head) {
176  head->call();
177  head->destroy();
178  }
179  return !is_active();
180  }
181  static void drop() {
182  if (head) head->destroy();
183  }
184  static bool is_active() {
185  // returns true if a macro is running.
186  return head != NULp;
187  }
188 };
189 
190 ExecutingMacro *ExecutingMacro::head = NULp;
191 
192 static void macro_terminated(GBDATA *gb_terminated, GB_CB_TYPE IF_ASSERTION_USED(cb_type)) {
193  ma_assert(cb_type == GB_CB_CHANGED);
194  fprintf(stderr, "macro_terminated called\n");
195  bool allMacrosTerminated = ExecutingMacro::done();
196  if (allMacrosTerminated) {
197  fprintf(stderr, "macro_terminated: allMacrosTerminated\n");
198  GBDATA *gb_main = GB_get_root(gb_terminated);
200  aw_message_if(error);
201 
202  // check for global macro error
203  GB_ERROR macro_error = GB_get_macro_error(gb_main);
204  if (macro_error) {
205  aw_message_if(macro_error);
206  aw_message("Warning: macro terminated (somewhere in the middle)");
207 
208  GB_ERROR clr_error = GB_clear_macro_error(gb_main);
209  if (clr_error) fprintf(stderr, "Warning: failed to clear macro error (Reason: %s)\n", clr_error);
210  }
211  }
212 }
213 
214 GB_ERROR MacroRecorder::execute(const char *macroFile, bool loop_marked, const RootCallback& execution_done_cb) {
215  GB_ERROR error = NULp;
216  {
218  GB_transaction ta(gb_main);
219 
220  GBDATA *gb_term = GB_search(gb_main, MACRO_TRIGGER_TERMINATED, GB_FIND);
221  if (!gb_term) {
222  gb_term = GB_search(gb_main, MACRO_TRIGGER_TERMINATED, GB_INT);
223  if (!gb_term) {
224  error = GB_await_error();
225  }
226  else {
227  GB_add_callback(gb_term, GB_CB_CHANGED, makeDatabaseCallback(macro_terminated));
228  }
229  }
230  error = ta.close(error);
231  }
232 
233  if (!error) {
234  ExecutingMacro::add(execution_done_cb);
235  error = GBT_macro_execute(macroFile, loop_marked, true);
236  if (error) ExecutingMacro::drop(); // avoid double free
237  }
238 
239  return error;
240 }
241 
242 void MacroRecorder::track_action(const char *action_id) {
244  recording->track_action(action_id);
245 }
246 
249  recording->track_awar_change(awar);
250 }
251 
253  ma_assert(tracked && tracked[0]);
254 
255  GB_ERROR error = NULp;
256  if (tracked && tracked[0]) {
257  char *saveptr = NULp;
258  const char *app_id = strtok_r(tracked, "*", &saveptr);
259  const char *cmd = strtok_r(NULp, "*", &saveptr);
260  char *rest = strtok_r(NULp, "", &saveptr);
261 
262  if (recording) {
263  if (strcmp(cmd, "AWAR") == 0) {
264  const char *awar_name = strtok_r(rest, "*", &saveptr);
265  const char *content = strtok_r(NULp, "", &saveptr);
266 
267  if (!content) content = "";
268 
269  recording->write_awar_change(app_id, awar_name, content);
270  }
271  else if (strcmp(cmd, "ACTION") == 0) {
272  recording->write_action(app_id, rest);
273  }
274  else {
275  error = GBS_global_string("Unknown client action '%s'", cmd);
276  }
277  }
278  else {
279  fprintf(stderr, "Warning: tracked action '%s' from client '%s' (dropped because not recording)\n", cmd, app_id);
280  }
281  }
282 
283  return error;
284 }
285 
286 void MacroRecorder::add_planned_interruption(const char *displayed_text) {
288  recording->write_planned_interruption(displayed_text);
289 }
290 
292  return ExecutingMacro::is_active();
293 }
294 
295 // -----------------------------
296 // ClientActionTracker
297 
299  bool recording = GB_read_int(gb_recording);
300  if (is_tracking() != recording) set_recording(recording);
301 }
302 
304  cat->set_tracking_according_to(gb_recording);
305 }
306 
307 void ClientActionTracker::bind_callbacks(bool install) {
309  GB_ERROR error = NULp;
311 
312  if (!gb_recording) {
313  error = GB_await_error();
314  }
315  else {
316  if (install) {
317  error = GB_add_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
318  record_state_changed_cb(gb_recording, this); // call once
319  }
320  else {
321  GB_remove_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
322  }
323  }
324 
325  if (error) {
326  aw_message(GBS_global_string("Failed to %s ClientActionTracker: %s", install ? "init" : "cleanup", error));
327  }
328 }
329 
330 
331 void ClientActionTracker::track_action(const char *action_id) {
332  if (!action_id) {
333  warn_unrecordable("anonymous GUI element");
334  }
335  else {
336  ma_assert(!strchr(action_id, '*'));
337  send_client_action(GBS_global_string("ACTION*%s", action_id));
338  }
339 }
340 
342  // see also recmac.cxx@AWAR_CHANGE_TRACKING
343 
344  char *svalue = awar->read_as_string();
345  if (!svalue) {
346  warn_unrecordable(GBS_global_string("change of '%s'", awar->awar_name));
347  }
348  else {
349  ma_assert(!strchr(awar->awar_name, '*'));
350  send_client_action(GBS_global_string("AWAR*%s*%s", awar->awar_name, svalue));
351  free(svalue);
352  }
353 }
354 
355 void ClientActionTracker::send_client_action(const char *action) {
356  // action is either
357  // "ACTION*<actionId>" or
358  // "AWAR*<awarName>*<awarValue>"
359 
360  // send action
361  GB_ERROR error;
362  GBDATA *gb_clientTrack = NULp;
363  {
364  error = GB_begin_transaction(get_gbmain());
365  if (!error) {
367  if (!gb_clientTrack) error = GB_await_error();
368  else {
369  const char *prev_track = GB_read_char_pntr(gb_clientTrack);
370 
371  if (!prev_track) error = GB_await_error();
372  else if (prev_track[0]) error = GBS_global_string("Cant send_client_action: have pending client action (%s)", prev_track);
373 
374  if (!error) {
375  ma_assert(!strchr(get_application_id(), '*'));
376  error = GB_write_string(gb_clientTrack, GBS_global_string("%s*%s", get_application_id(), action));
377  }
378  }
379  }
380  error = GB_end_transaction(get_gbmain(), error);
381  }
382 
383  if (!error) {
384  // wait for recorder to consume action
385  bool consumed = false;
386  int count = 0;
387  MacroTalkSleep increasing;
388 
389  while (!consumed && !error) {
390  increasing.sleep();
391  ++count;
392  if ((count%25) == 0) {
393  fprintf(stderr, "[Waiting for macro recorder to consume action tracked by %s]\n", get_application_id());
394  }
395 
396  error = GB_begin_transaction(get_gbmain());
397 
398  const char *track = GB_read_char_pntr(gb_clientTrack);
399  if (!track) error = GB_await_error();
400  else consumed = !track[0];
401 
402  error = GB_end_transaction(get_gbmain(), error);
403  }
404  }
405 
406  if (error) {
407  aw_message(GBS_global_string("Failed to record client action (Reason: %s)", error));
408  }
409 }
410 
411 void ClientActionTracker::ungrant_client_and_confirm_quit_action() {
412  // shutdown macro client
413  // - confirm action (needed in case the quit has been triggered by a macro; otherwise macro hangs forever)
414  // - unauthorize this process for macro execution
415 
417  GB_transaction ta(gb_main);
419  GB_ERROR error = NULp;
420 
421  GBDATA *gb_granted = GB_search(gb_main, remote.granted(), GB_FIND);
422  if (gb_granted) {
423  pid_t pid = getpid();
424  pid_t granted_pid = GB_read_int(gb_granted);
425 
426  if (pid == granted_pid) { // this is the client with macro execution rights
427  GBDATA *gb_action = GB_search(gb_main, remote.action(), GB_FIND);
428  if (gb_action) error = GB_write_string(gb_action, ""); // signal macro, that action was executed
429  if (!error) error = GB_write_int(gb_granted, 0); // un-authorize this process
430  }
431  }
432 
433  if (error) {
434  error = GB_set_macro_error(gb_main, GBS_global_string("error during client quit: %s", error));
435  if (error) fprintf(stderr, "Error in ungrant_client_and_confirm_quit_action: %s\n", error);
436  }
437 
438  if (is_tracking()) set_recording(false);
439 }
440 
441 // -------------------------
442 // tracker factory
443 
444 static UserActionTracker *make_macro_recording_tracker(const char *client_id, GBDATA *gb_main) {
445  // 'client_id' has to be a unique id (used to identify the program which will record/playback).
446  // If multiple programs (or multiple instances of one) use the same id, macro recording shall abort.
447  // If a program is used for different purposes by starting multiple instances (like e.g. arb_ntree),
448  // each purpose/instance should use a different 'client_id'.
449 
450  ma_assert(gb_main);
451  ma_assert(client_id && client_id[0]);
452 
453  BoundActionTracker *tracker;
454  if (GB_is_server(gb_main)) {
455  tracker = new MacroRecorder(client_id, gb_main);
456  }
457  else {
458  tracker = new ClientActionTracker(client_id, gb_main);
459  }
460  return tracker;
461 }
462 
464  return new RequiresActionTracker;
465 }
466 
467 GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main) {
468  ma_assert(aw_root);
469 
471  GB_ERROR error = NULp;
472  if (existing && existing->reconfigure(client_id, gb_main)) {
473  error = reconfigure_dbserver(client_id, gb_main);
474  }
475  else {
476  aw_root->setUserActionTracker(make_macro_recording_tracker(client_id, gb_main));
477  error = startup_dbserver(aw_root, client_id, gb_main);
478  }
479 
480  return error;
481 }
482 
485  if (tracker) tracker->release();
486 }
487 
488 bool got_macro_ability(AW_root *aw_root) {
489  // return true if aw_root has a BoundActionTracker
490  return get_active_macro_recording_tracker(aw_root);
491 }
492 
#define ma_assert(bed)
Definition: macro_gui.cxx:28
GB_ERROR GB_begin_transaction(GBDATA *gbd)
Definition: arbdb.cxx:2528
void track_action(const char *action_id)
Definition: recmac.cxx:161
const char * GB_ERROR
Definition: arb_core.h:25
GB_ERROR start_recording(const char *file, const char *stop_action_name, bool expand_existing)
Definition: trackers.cxx:74
#define MACRO_TRIGGER_TRACKED
Definition: arbdbt.h:27
bool is_executing_macro() const
Definition: trackers.cxx:291
void put(const char *elem)
Definition: arb_strarray.h:188
size_t size() const
Definition: arb_strarray.h:85
GB_ERROR GB_set_macro_error(GBDATA *gb_main, const char *curr_error)
Definition: adtools.cxx:670
long GB_read_int(GBDATA *gbd)
Definition: arbdb.cxx:729
GBDATA * GB_child(GBDATA *father)
Definition: adquery.cxx:322
static void macro_terminated(GBDATA *gb_terminated, GB_CB_TYPE cb_type)
Definition: trackers.cxx:192
bool is_tracking() const
Definition: aw_root.hxx:70
bool GB_is_server(GBDATA *gbd)
Definition: adcomm.cxx:1697
const char * granted() const
Definition: ad_remote.h:80
GB_ERROR GB_write_string(GBDATA *gbd, const char *s)
Definition: arbdb.cxx:1387
GBDATA * GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value)
Definition: adquery.cxx:546
GB_ERROR GB_add_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:356
void track_awar_change(AW_awar *awar)
Definition: recmac.cxx:180
GB_ERROR GBT_macro_execute(const char *macro_name, bool loop_marked, bool run_async)
Definition: adtools.cxx:919
GB_ERROR GB_end_transaction(GBDATA *gbd, GB_ERROR error)
Definition: arbdb.cxx:2561
GB_ERROR execute(const char *macroFile, bool loop_marked, const RootCallback &execution_done_cb)
Definition: trackers.cxx:214
void sleep()
Definition: arb_sleep.h:94
static UserActionTracker * make_macro_recording_tracker(const char *client_id, GBDATA *gb_main)
Definition: trackers.cxx:444
const char * authAck() const
Definition: ad_remote.h:79
#define MACRO_TRIGGER_TERMINATED
Definition: arbdbt.h:24
void track_awar_change(AW_awar *awar) OVERRIDE
Definition: trackers.cxx:247
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
void track_awar_change(AW_awar *awar) OVERRIDE
Definition: trackers.cxx:341
UserActionTracker * need_macro_ability()
Definition: trackers.cxx:463
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:242
void GBK_terminatef(const char *templat,...)
Definition: arb_msg.cxx:523
void shutdown_macro_recording(AW_root *aw_root)
Definition: trackers.cxx:483
static bool is_active()
Definition: trackers.cxx:184
const char * authReq() const
Definition: ad_remote.h:78
virtual void release()=0
GB_ERROR stop()
Definition: recmac.cxx:199
void setUserActionTracker(UserActionTracker *user_tracker)
Definition: AW_root.cxx:210
GB_ERROR reconfigure_dbserver(const char *application_id, GBDATA *gb_main)
Definition: dbserver.cxx:292
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:342
static AW_root * SINGLETON
Definition: aw_root.hxx:102
void add_planned_interruption(const char *displayed_text)
Definition: trackers.cxx:286
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:331
GB_CSTR GB_read_key_pntr(GBDATA *gbd)
Definition: arbdb.cxx:1656
void set_tracking_according_to(GBDATA *gb_recording)
Definition: trackers.cxx:298
static void add(const RootCallback &execution_done_cb)
Definition: trackers.cxx:172
bool got_macro_ability(AW_root *aw_root)
Definition: trackers.cxx:488
static void error(const char *msg)
Definition: mkptypes.cxx:96
GBDATA * GB_get_root(GBDATA *gbd)
Definition: arbdb.cxx:1740
GBDATA * GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value)
Definition: adquery.cxx:569
char * read_as_string() const
static __ATTR__USERESULT GB_ERROR clearMacroExecutionAuthorization(GBDATA *gb_main)
Definition: trackers.cxx:135
bool reconfigure(const char *application_id, GBDATA *gb_main)
Definition: trackers.cxx:24
static void record_state_changed_cb(GBDATA *gb_recording, ClientActionTracker *cat)
Definition: trackers.cxx:303
void write_awar_change(const char *app_id, const char *awar_name, const char *content)
Definition: recmac.cxx:144
#define REMOTE_BASE
Definition: arbdbt.h:22
static GB_ERROR announce_recording(GBDATA *gb_main, int record)
Definition: trackers.cxx:68
GB_ERROR startup_dbserver(AW_root *aw_root, const char *application_id, GBDATA *gb_main)
Definition: dbserver.cxx:252
static void getKnownMacroClients(ConstStrArray &clientNames, GBDATA *gb_main)
Definition: trackers.cxx:114
Definition: arbdb.h:86
#define MACRO_TRIGGER_CONTAINER
Definition: arbdbt.h:23
static char * cat(char *toBuf, const char *s1, const char *s2)
Definition: ED4_root.cxx:1023
GB_ERROR GB_write_int(GBDATA *gbd, long i)
Definition: arbdb.cxx:1250
static void drop()
Definition: trackers.cxx:181
void set_recording(bool recording)
Definition: trackers.cxx:31
char * awar_name
Definition: aw_awar.hxx:103
void write_planned_interruption(const char *displayed_text)
Definition: recmac.cxx:153
GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main)
Definition: trackers.cxx:467
#define IF_ASSERTION_USED(x)
Definition: arb_assert.h:308
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:35
static bool done()
Definition: trackers.cxx:173
const char * get_application_id() const
Definition: trackers.hxx:67
GB_ERROR handle_tracked_client_action(char *&tracked)
Definition: trackers.cxx:252
#define __ATTR__USERESULT
Definition: attributes.h:58
#define MACRO_TRIGGER_RECORDING
Definition: arbdbt.h:25
void GB_remove_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:360
BoundActionTracker * get_active_macro_recording_tracker(AW_root *aw_root)
Definition: trackers.hxx:143
void write_action(const char *app_id, const char *action_name)
Definition: recmac.cxx:104
void aw_message(const char *msg)
Definition: AW_status.cxx:1142
const char * recAuth() const
Definition: ad_remote.h:82
GB_ERROR GB_clear_macro_error(GBDATA *gb_main)
Definition: adtools.cxx:699
#define NULp
Definition: cxxforward.h:116
GB_ERROR stop_recording()
Definition: trackers.cxx:91
__ATTR__USERESULT GB_ERROR setIntEntryToZero(GBDATA *gb_main, const char *entryPath)
Definition: trackers.cxx:130
GB_ERROR has_error() const
Definition: recmac.hxx:52
GBDATA * GB_nextChild(GBDATA *child)
Definition: adquery.cxx:326
void warn_unrecordable(const char *what)
Definition: recmac.cxx:30
GBDATA * get_gbmain()
Definition: trackers.hxx:66
GB_transaction ta(gb_var)
GB_CSTR GB_read_char_pntr(GBDATA *gbd)
Definition: arbdb.cxx:904
GBDATA * gb_main
Definition: adname.cxx:32
GBDATA * GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create)
Definition: adquery.cxx:531
GB_CB_TYPE
Definition: arbdb_base.h:46
GB_ERROR GB_get_macro_error(GBDATA *gb_main)
Definition: adtools.cxx:687
void aw_message_if(GB_ERROR error)
Definition: aw_msg.hxx:21
Definition: arbdb.h:66