source: trunk/SL/MACROS/MacroExitor.hxx

Last change on this file was 19662, checked in by westram, 13 days ago
File size: 7.1 KB
Line 
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
29class 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
120public:
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
Note: See TracBrowser for help on using the repository browser.