| 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) { |
|---|
| 65 | return TRY_TO_QUIT_ARB_FREQUENCY; |
|---|
| 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 | |
|---|
| 128 | void maybe_exit_delayed() { |
|---|
| 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 | |
|---|
| 156 | aw_root->add_timed_callback(TRY_TO_QUIT_ARB_FREQUENCY, |
|---|
| 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 |
|---|