ARB
MacroExitor.hxx
Go to the documentation of this file.
1 // ========================================================= //
2 // //
3 // File : MacroExitor.hxx //
4 // Purpose : Safe program exit via macro //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in Nov 25 //
7 // http://www.arb-home.de/ //
8 // //
9 // ========================================================= //
10 
11 #ifndef AW_ROOT_HXX
12 #include <aw_root.hxx>
13 #endif
14 #ifndef AW_MSG_HXX
15 #include <aw_msg.hxx>
16 #endif
17 #ifndef AW_QUESTION_HXX
18 #include <aw_question.hxx>
19 #endif
20 #ifndef ARB_SLEEP_H
21 #include <arb_sleep.h>
22 #endif
23 
24 #ifndef MACROEXITOR_HXX
25 #define MACROEXITOR_HXX
26 
27 #define TRY_TO_QUIT_ARB_FREQUENCY 250 // ms
28 
29 class MacroExitor : virtual Noncopyable {
30  AW_root *aw_root;
31  bool suppressed; // true after first exit attempt
32  const char *quitWhat; // name of application to quit (e.g. "arb")
33 
34  __ATTR__NORETURN virtual void perform_exit() = 0; // perform_exit() may NOT return.
35  virtual GBDATA *get_gbmain_checked_for_save() = 0; // shall return database which will be checked for saving.
36 
37  bool need_to_delay_exit() const { return is_executing_macro_as_server(aw_root); }
38 
39  __ATTR__NORETURN void perform_exit_or_terminate() {
40  perform_exit();
41  GBK_terminate("The exit mechanism didn't work, but resistance is futile.");
42  }
43 
44  unsigned exit_delayed() {
45  if (need_to_delay_exit()) {
46  // use one global timeout (exitors might be nested, but exit only happens once)
47  static long milliSeconds = 0;
48  static int lastDecaSeconds = 0;
49 
50  milliSeconds += TRY_TO_QUIT_ARB_FREQUENCY;
51  int seconds = milliSeconds/1000;
52  int decaSeconds = seconds/10;
53 
54  if (decaSeconds != lastDecaSeconds) {
55  const char *msg = GBS_global_string("waited %i seconds for macro termination", seconds);
56  if (decaSeconds>3) {
57  int after = (10-decaSeconds)*10;
58  msg = GBS_global_string("%s. Will terminate unconditionally in %i seconds", msg, after);
59  }
60  aw_message(GBS_global_string("[%s]", msg));
61  lastDecaSeconds = decaSeconds;
62  }
63 
64  if (decaSeconds<10) {
66  }
67  // reached after 100s
68  aw_message(GBS_global_string("[forcing %s quit]", quitWhat));
69  }
70  else {
71  aw_message(GBS_global_string("[all macros have terminated. %s quits now]", quitWhat));
72  }
73  ARB_sleep(1, SEC);
74  perform_exit_or_terminate();
75  return 0; // never reached
76  }
77  static unsigned exit_delayed(AW_root*, MacroExitor *exitor) { return exitor->exit_delayed(); }
78 
79  static bool user_does_confirm_quit__if_server(GBDATA *gb_main, const char *appName) {
80  bool shallQuit = true;
81 
82  if (gb_main && // we have a database (e.g. have none intro window)
83  GB_read_clients(gb_main) >= 0 && // only ask, if arb is server.
84  GB_read_clock(gb_main) > GB_last_saved_clock(gb_main))
85  {
86  long secs = GB_last_saved_time(gb_main);
87  char *quit_buttons = GBS_global_string_copy("Quit %s,Do NOT quit", appName);
88  char *question = NULp;
89  if (secs) {
90  secs = GB_time_of_day() - secs;
91  if (secs>15) {
92  question = GBS_global_string_copy("You last saved your data %li:%02li minutes ago\nSure to quit?", secs/60, secs%60);
93  }
94  }
95  else {
96  question = ARB_strdup("You never saved any data.\nSure to quit?");
97  }
98 
99 #if defined(DEBUG)
100  if (question) {
101  freeset(question, GBS_string_eval(question, ":\n=;"));
102  freeset(quit_buttons, GBS_string_eval(quit_buttons, ":\n=;"));
103  fprintf(stderr, "[NDEBUG version would query user with: '%s' -> '%s']\n", question, quit_buttons);
104  }
105  else {
106  fprintf(stderr, "[NDEBUG version would also quit %s w/o query]\n", appName);
107  }
108  freenull(question);
109 #endif
110 
111  if (question) {
112  shallQuit = aw_question("quit_arb", question, quit_buttons) == 0;
113  free(question);
114  }
115  free(quit_buttons);
116  }
117  return shallQuit;
118  }
119 
120 public:
121  MacroExitor(AW_root *aw_root_, const char *appName)
122  : aw_root(aw_root_),
123  suppressed(false),
124  quitWhat(appName)
125  {}
126  virtual ~MacroExitor() {}
127 
129  // Normally this method exits the program.
130  //
131  // When a macro is running, this method will return instantly.
132  // The program waits until the macro terminates, and then exits the program
133  // (using perform_exit() from the derived class).
134  //
135  // Otherwise, if no macro is running, the user will be asked whether he is sure to quit.
136  // If the database runs as client, this question will be auto-confirmed, because some server is still running.
137  //
138  // When confirmed, the program instantly exits.
139  // Otherwise the method returns.
140  //
141  // The two cases if which the method returns can be distinguished using the
142  // result of is_performing_delayed_exit():
143  // - if true, the program will automatically exit (delayed).
144  // - if false, the user did not confirm to quit. In this case 'this' should be deleted,
145  // to avoid inconsistent behavior.
146 
147  if (need_to_delay_exit()) {
148  if (!suppressed) {
149  aw_message(GBS_global_string("[did not quit %s yet, because macro is still running. Would crash. Will retry ASAP]", quitWhat));
150  suppressed = true;
151  }
152  else {
153  aw_message("[macro is still running. But time will pass faster because you are in a hurry]");
154  }
155 
157  makeTimedCallback(MacroExitor::exit_delayed, this));
158  }
159  else {
160  // if control flow comes here,
161  // - this app is either running as CLIENT (for the database used for macro execution), or
162  // - it is a SERVER, and no macro is running (in that case the if branch above is chosen).
163  //
164  // Note:
165  // - CLIENTS always quit w/o question
166  // - SERVERS do this only during development,
167  // otherwise the user is asked if he is sure to quit.
168 
169  if (user_does_confirm_quit__if_server(get_gbmain_checked_for_save(), quitWhat)) {
170  perform_exit_or_terminate();
171  }
172  }
173  }
174 
175  AW_root *get_root() const { return aw_root; }
176  bool is_performing_delayed_exit() const { return suppressed; }
177 };
178 
179 
180 #else
181 #error MacroExitor.hxx included twice
182 #endif // MACROEXITOR_HXX
void maybe_exit_delayed()
char * ARB_strdup(const char *str)
Definition: arb_string.h:27
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
char * GBS_string_eval(const char *insource, const char *icommand)
Definition: admatch.cxx:699
void add_timed_callback(int ms, const TimedCallback &tcb)
Definition: AW_root.cxx:552
bool is_performing_delayed_exit() const
AliDataPtr after(AliDataPtr data, size_t pos)
Definition: insdel.cxx:593
void GBK_terminate(const char *error) __ATTR__NORETURN
Definition: arb_msg.cxx:509
#define false
Definition: ureadseq.h:13
GB_ULONG GB_time_of_day(void)
Definition: adsocket.cxx:358
long GB_read_clients(GBDATA *gbd)
Definition: adcomm.cxx:1682
void ARB_sleep(int amount, TimeUnit tu)
Definition: arb_sleep.h:32
long GB_last_saved_clock(GBDATA *gb_main)
int aw_question(const char *unique_id, const char *question, const char *buttons, bool sameSizeButtons, const char *helpfile)
Definition: AW_question.cxx:26
GB_ULONG GB_last_saved_time(GBDATA *gb_main)
#define TRY_TO_QUIT_ARB_FREQUENCY
Definition: MacroExitor.hxx:27
bool is_executing_macro_as_server(AW_root *root)
Definition: macro_gui.cxx:287
void aw_message(const char *msg)
Definition: AW_status.cxx:1142
long GB_read_clock(GBDATA *gbd)
Definition: arbdb.cxx:1714
MacroExitor(AW_root *aw_root_, const char *appName)
#define NULp
Definition: cxxforward.h:116
#define __ATTR__NORETURN
Definition: attributes.h:56
GBDATA * gb_main
Definition: adname.cxx:32
AW_root * get_root() const
virtual ~MacroExitor()
Definition: arb_sleep.h:30
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194