source: branches/stable/NTREE/NT_main.cxx

Last change on this file was 18693, checked in by westram, 3 years ago
  • link from intro help to license.
  • correct copyrights in intro window, license + CLI help.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.1 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : NT_main.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "NT_local.h"
12
13#include <mg_merge.hxx>
14#include <awti_import.hxx>
15#include <insdel.h>
16#include <macros.hxx>
17#include <mod_rlimit.h>
18#include <TreeDisplay.hxx>
19
20#include <awt.hxx>
21#include <awt_misc.hxx>
22#include <awt_sel_boxes.hxx>
23#include <awt_TreeAwars.hxx>
24
25#include <aw_advice.hxx>
26#include <aw_question.hxx>
27#include <aw_awars.hxx>
28#include <aw_edit.hxx>
29#include <aw_file.hxx>
30#include <aw_msg.hxx>
31#include <aw_root.hxx>
32
33#include <adGene.h>
34
35#include <arb_version.h>
36#include <arb_progress.h>
37#include <arb_file.h>
38
39#include <signal.h>
40
41using namespace std;
42
43AW_HEADER_MAIN
44
45NT_global GLOBAL;
46
47// NT_format_all_alignments may be called after any operation which causes
48// unformatted alignments (e.g importing sequences)
49//
50// It tests all alignments whether they need to be formatted
51// and asks the user if they should be formatted.
52
53GB_ERROR NT_format_all_alignments(GBDATA *gb_main) {
54    GB_ERROR            err = NULp;
55    GB_transaction      ta(gb_main);
56    GB_topSecurityLevel unsecured(gb_main);
57
58    long ali_count = GBT_count_alignments(gb_main);
59    if (ali_count) {
60        arb_progress progress("Formatting alignments", ali_count);
61        err = GBT_check_data(gb_main, NULp);
62
63        AW_repeated_question question;
64        question.add_help("prompt/format_alignments.hlp");
65
66        GBDATA *gb_presets = GBT_get_presets(gb_main);
67        for (GBDATA *gb_ali = GB_entry(gb_presets, "alignment");
68             gb_ali && !err;
69             gb_ali        = GB_nextEntry(gb_ali))
70        {
71            GBDATA *gb_aligned = GB_search(gb_ali, "aligned", GB_INT);
72
73            if (GB_read_int(gb_aligned) == 0) { // sequences in alignment are not formatted
74                enum FormatAction {
75                    FA_ASK_USER   = 0, // ask user
76                    FA_FORMAT_ALL = 1, // format automatically w/o asking
77                    FA_SKIP_ALL   = 2, // skip automatically w/o asking
78                };
79                FormatAction  format_action = FA_ASK_USER;
80                GBDATA       *gb_ali_name   = GB_entry(gb_ali, "alignment_name");
81                const char   *ali_name      = GB_read_char_pntr(gb_ali_name);
82
83                {
84                    bool    is_ali_genom   = strcmp(ali_name, GENOM_ALIGNMENT) == 0;
85                    GBDATA *gb_auto_format = GB_entry(gb_ali, "auto_format");
86
87                    if (gb_auto_format) {
88                        format_action = FormatAction(GB_read_int(gb_auto_format));
89                        if (is_ali_genom) {
90                            if (format_action != FA_SKIP_ALL) {
91                                format_action = FA_SKIP_ALL; // always skip ali_genom
92                                err           = GB_write_int(gb_auto_format, FA_SKIP_ALL);
93                            }
94                        }
95                    }
96                    else if (is_ali_genom) {
97                        format_action = FA_SKIP_ALL;
98                        err           = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);  // always skip
99                    }
100                }
101
102                bool perform_format = false;
103                if (!err) {
104                    switch (format_action) {
105                        case FA_FORMAT_ALL: perform_format = true; break;
106                        case FA_SKIP_ALL:   perform_format = false; break;
107                        default: {
108                            char *qtext  = GBS_global_string_copy("Alignment '%s' is not formatted. Format?", ali_name);
109                            int   answer = question.get_answer("format_alignments", qtext, "Format,Skip,Always format,Always skip", "all", false);
110
111                            switch (answer) {
112                                case 2:
113                                    err = GBT_write_int(gb_ali, "auto_format", FA_FORMAT_ALL);
114                                    // fall-through
115                                case 0:
116                                    perform_format = true;
117                                    break;
118
119                                case 3:
120                                    err = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);
121                                    break;
122                            }
123
124                            free(qtext);
125                            break;
126                        }
127                    }
128                }
129                if (!err && perform_format) {
130                    err = ARB_format_alignment(gb_main, ali_name);
131                }
132            }
133            progress.inc_and_check_user_abort(err);
134        }
135
136        if (err) progress.done();
137    }
138
139    err = ta.close(err);
140    return err;
141}
142
143
144// --------------------------------------------------------------------------------
145
146
147static GB_ERROR nt_check_database_consistency() {
148    // called once on ARB_NTREE startup
149    arb_progress cons_progress("Checking consistency");
150
151    GB_ERROR err  = NT_format_all_alignments(GLOBAL.gb_main);
152    if (!err) err = NT_repair_DB(GLOBAL.gb_main);
153
154    return err;
155}
156
157__ATTR__USERESULT static GB_ERROR startup_mainwindow_and_dbserver(AW_root *aw_root, const char *autorun_macro, TREE_canvas*& result_ntw) {
158    AWT_initTreeAwarRegistry(GLOBAL.gb_main);
159
160    GB_ERROR error = configure_macro_recording(aw_root, "ARB_NT", GLOBAL.gb_main); // @@@ problematic if called from startup-importer
161    if (!error) {
162        result_ntw = NT_create_main_window(aw_root);
163        if (GB_is_server(GLOBAL.gb_main)) {
164            error = nt_check_database_consistency();
165            if (!error) NT_repair_userland_problems();
166        }
167    }
168
169    if (!error) error = GBK_system("arb_macro_compatibility.sh &"); // run async to avoid (DB) lock problems
170
171    if (!error && autorun_macro) execute_macro(aw_root, autorun_macro); // @@@ triggering execution here is ok, but its a bad place to pass 'autorun_macro'. Should be handled more generally
172
173    return error;
174}
175
176static ARB_ERROR load_and_startup_main_window(AW_root *aw_root, const char *autorun_macro) {
177    char *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
178    GLOBAL.gb_main  = GBT_open(db_server, "rw");
179
180    ARB_ERROR error;
181    if (!GLOBAL.gb_main) {
182        error = GB_await_error();
183    }
184    else {
185        aw_root->awar(AWAR_DB_PATH)->write_string(db_server);
186
187#define MAXNAMELEN 35
188        int len = strlen(db_server);
189        if (len>MAXNAMELEN) {
190            char *nameOnly = strrchr(db_server, '/');
191            if (nameOnly) {
192                nameOnly++;
193                len -= (nameOnly-db_server);
194                memmove(db_server, nameOnly, len+1);
195                if (len>MAXNAMELEN) {
196                    strcpy(db_server+MAXNAMELEN-3, "...");
197                }
198            }
199        }
200#if defined(DEBUG)
201        AWT_announce_db_to_browser(GLOBAL.gb_main, GBS_global_string("ARB database (%s)", db_server));
202#endif // DEBUG
203
204        TREE_canvas *dummy   = NULp;
205        GB_ERROR     problem = startup_mainwindow_and_dbserver(aw_root, autorun_macro, dummy);
206        aw_message_if(problem); // no need to terminate ARB
207    }
208
209    free(db_server);
210    return error;
211}
212
213#define AWAR_DB_FILTER    AWAR_DBBASE "/filter"
214#define AWAR_DB_DIRECTORY AWAR_DBBASE "/directory"
215
216static void nt_delete_database(AW_window *aww) {
217    AW_root *aw_root   = aww->get_root();
218    char    *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
219
220    if (strlen(db_server)) {
221        if (aw_ask_sure(NULp, GBS_global_string("Are you sure to delete database %s\nNote: there is no way to undelete it afterwards", db_server))) {
222            GB_ERROR error = GB_delete_database(db_server);
223            if (error) {
224                aw_message(error);
225            }
226            else {
227                aw_root->awar(AWAR_DB_FILTER)->touch();
228            }
229        }
230    }
231    else {
232        aw_message("No database selected");
233    }
234    free(db_server);
235}
236
237static void start_main_window_after_import(AW_root *aw_root) {
238    GLOBAL.aw_root = aw_root;
239
240    GBDATA *gb_imported = AWTI_acquire_imported_DB_and_cleanup_importer();
241    nt_assert(gb_imported == GLOBAL.gb_main); // import-DB should already be used as main-DB
242    GLOBAL.gb_main      = gb_imported;
243
244    TREE_canvas *ntw = NULp;
245    aw_message_if(startup_mainwindow_and_dbserver(aw_root, NULp, ntw));
246
247    if (aw_root->awar(AWAR_IMPORT_AUTOCONF)->read_int()) {
248        NT_create_config_after_import(ntw, true);
249    }
250
251    aw_root->awar(AWAR_TREE_REFRESH)->touch();
252}
253
254static void nt_intro_start_existing(AW_window *aw_intro) {
255    aw_intro->hide();
256    ARB_ERROR error = load_and_startup_main_window(aw_intro->get_root(), NULp);
257    nt_assert(contradicted(error, got_macro_ability(aw_intro->get_root())));
258    if (error) {
259        aw_intro->show();
260        aw_popup_ok(error.deliver());
261    }
262    else {
263        error.expect_no_error();
264    }
265}
266
267static void nt_intro_start_merge(AW_window *aw_intro) {
268    AW_root    *aw_root    = aw_intro->get_root();
269    const char *dir        = aw_root->awar(AWAR_DB_DIRECTORY)->read_char_pntr();
270    char       *merge_args = GBS_global_string_copy("'%s' '%s'", dir, dir);
271
272    NT_restart(aw_root, merge_args); //  call arb_ntree as merge-tool on exit
273}
274
275static void nt_intro_start_import(AW_window *aw_intro) {
276    aw_intro->hide();
277
278    AW_root *aw_root = aw_intro->get_root();
279    aw_root->awar_string(AWAR_DB_PATH)->write_string("noname.arb");
280
281    AWTI_open_import_window(aw_root, NULp, true, NULp, makeRootCallback(start_main_window_after_import));
282    AWTI_set_importDB_pointer(GLOBAL.gb_main);
283
284    nt_assert(got_macro_ability(aw_root));
285}
286
287static AW_window *nt_create_intro_window(AW_root *awr) {
288    AW_window_simple *aws = new AW_window_simple;
289    aws->init(awr, "ARB_INTRO", "ARB INTRO");
290    aws->load_xfig("arb_intro.fig");
291
292    aws->callback(makeWindowCallback(NT_exit, EXIT_SUCCESS));
293    aws->at("close");
294    aws->create_button("EXIT", "Exit", "x");
295
296    aws->at("help");
297    aws->callback(makeHelpCallback("arb_intro.hlp"));
298    aws->create_button("HELP", "HELP", "H");
299
300    AW_create_standard_fileselection(aws, AWAR_DBBASE);
301
302    aws->button_length(0);
303
304    aws->at("logo");
305    aws->create_button(NULp, "#logo.xpm");
306
307    aws->at("version");
308    aws->create_button(NULp, GBS_global_string("Version " ARB_VERSION "\n(" ARB_BUILD_DATE ")")); // version + date
309
310    aws->at("copyright");
311    aws->create_button(NULp, GBS_global_string("(C) 1993-" ARB_BUILD_YEAR));
312
313    aws->at("old");
314    aws->callback(nt_intro_start_existing);
315    aws->create_autosize_button("OPEN_SELECTED", "OPEN SELECTED", "O");
316
317    aws->at("del");
318    aws->callback(nt_delete_database);
319    aws->create_autosize_button("DELETE_SELECTED", "DELETE SELECTED");
320
321    aws->at("new_complex");
322    aws->callback(nt_intro_start_import);
323    aws->create_autosize_button("CREATE_AND_IMPORT", "CREATE AND IMPORT", "I");
324
325    aws->at("merge");
326    aws->callback(nt_intro_start_merge);
327    aws->create_autosize_button("MERGE_TWO_DATABASES", "MERGE TWO ARB DATABASES", "O");
328
329    aws->at("expert");
330    aws->create_toggle(AWAR_EXPERT);
331
332    return aws;
333}
334
335static void AWAR_DB_PATH_changed_cb(AW_root *awr) {
336    static bool avoid_recursion = false;
337
338    if (!avoid_recursion) {
339        LocallyModify<bool> flag(avoid_recursion, true);
340
341        char *value  = awr->awar(AWAR_DB_PATH)->read_string();
342        char *lslash = strrchr(value, '/');
343
344        char *name = lslash ? lslash+1 : value;
345#if defined(DEBUG)
346        printf("writing '%s' to AWAR_DB_NAME\n", name);
347#endif // DEBUG
348        awr->awar(AWAR_DB_NAME)->write_string(name);
349
350        if (lslash) {               // update value of directory
351            lslash[0] = 0;
352            awr->awar(AWAR_DB_DIRECTORY)->write_string(value);
353            lslash[0] = '/';
354        }
355
356        free(value);
357    }
358}
359
360class NtreeCommandLine : virtual Noncopyable {
361    int               arg_count;
362    char const*const* args;
363
364    bool help_requested;
365    bool do_import;
366   
367    const char *macro_name;
368
369public:
370    NtreeCommandLine(int argc_, char const*const* argv_)
371        : arg_count(argc_-1),
372          args(argv_+1),
373          help_requested(false),
374          do_import(false),
375          macro_name(NULp)
376    {}
377
378    void shift() { ++args; --arg_count; }
379
380    int free_args() const { return arg_count; }
381    const char *get_arg(int num) const { return num<arg_count ? args[num] : NULp; }
382
383    bool wants_help() const { return help_requested; }
384    bool wants_import() const { return do_import; }
385    bool wants_merge() const { return arg_count == 2; }
386
387    const char *autorun_macro() const { return macro_name; }
388
389    void print_help(FILE *out) const {
390        fprintf(out,
391                "\n"
392                "arb_ntree version " ARB_VERSION_DETAILED "\n"
393                "(C) 1993-" ARB_BUILD_YEAR " The ARB-project\n"
394                "http://www.arb-home.de/\n"
395                "(version build by: " ARB_BUILD_USER "@" ARB_BUILD_HOST ")\n"
396                "\n"
397                "Possible usage:\n"
398                "  arb_ntree               => start ARB (intro)\n"
399                "  arb_ntree DB            => start ARB with DB\n"
400                "  arb_ntree DB1 DB2       => merge from DB1 into DB2\n"
401                "  arb_ntree --import FILE => import FILE into new database (FILE may be a filemask)\n"
402                "\n"
403                "Additional arguments possible with command lines above:\n"
404                "  --execute macroname     => execute macro 'macroname' after startup\n"
405                "\n"
406                "Each DB argument may be one of the following:\n"
407                "  database.arb            -> use existing or new database\n"
408                \":\"                     -> connect to database of a running instance of ARB\n"
409                "  directory               -> prompt for databases inside directory\n"
410                "  filemask                -> also prompt for DB, but more specific (e.g. \"prot*.arb\")\n"
411                "\n"
412            );
413    }
414
415    ARB_ERROR parse() {
416        ARB_ERROR error;
417
418        while (!error && arg_count>0 && args[0][0] == '-') {
419            const char *opt = args[0]+1;
420            if (opt[0] == '-') ++opt; // accept '-' or '--'
421            if (strcmp(opt, "help") == 0 || strcmp(opt, "h") == 0) { help_requested = true; }
422            else if (strcmp(opt, "execute") == 0) { shift(); macro_name = *args; }
423            else if (strcmp(opt, "import") == 0) { do_import = true; }
424
425            // bunch of test switches to provoke various ways to terminate (see also http://bugs.arb-home.de/ticket/538)
426            else if (strcmp(opt, "crash") == 0) { GBK_terminate("crash requested"); }
427            else if (strcmp(opt, "trap") == 0) { arb_assert(0); }
428            else if (strcmp(opt, "sighup") == 0) { raise(SIGHUP); }
429            else if (strcmp(opt, "sigsegv") == 0) { raise(SIGSEGV); }
430            else if (strcmp(opt, "sigterm") == 0) { raise(SIGTERM); }
431            else if (strcmp(opt, "fail") == 0) { exit(EXIT_FAILURE); }
432            else if (strcmp(opt, "exit") == 0) { exit(EXIT_SUCCESS); }
433            // end of test switches ----------------------------------------
434
435            else error = GBS_global_string("Unknown switch '%s'", args[0]);
436            shift();
437        }
438        // non-switch arguments remain in arg_count/args
439        if (!error) {
440            if (do_import && arg_count != 1) error = "expected exactly one file-name or file-mask behind --import";
441            else if (arg_count>2)            error = "too many stray arguments given (max. 2 accepted)";
442        }
443        return error;
444    }
445};
446
447enum ArgType { // args that might be specified on command line (for DB or FILE; see above)
448    RUNNING_DB,
449    DIRECTORY,
450    EXISTING_DB,
451    NEW_DB,
452    FILEMASK,
453    EXISTING_FILE,
454    UNKNOWN_ARG,
455};
456
457inline bool has_arb_suffix(const char *arg) {
458    const char *suffix = strstr(arg, ".arb");
459    if (suffix) {
460        return strcmp(suffix, ".arb") == 0;
461    }
462    return false;
463}
464
465static ArgType detectArgType(const char *arg) {
466    if (strcmp(arg, ":") == 0) return RUNNING_DB;
467    if (GB_is_directory(arg)) return DIRECTORY;
468    if (strpbrk(arg, "*?")) return FILEMASK;
469    if (has_arb_suffix(arg)) {
470        return GB_is_regularfile(arg) ? EXISTING_DB : NEW_DB;
471    }
472
473    GB_ERROR load_file_err = GBT_check_arb_file(arg);
474    if (!load_file_err) return EXISTING_DB;
475
476    return GB_is_regularfile(arg) ? EXISTING_FILE : UNKNOWN_ARG;
477}
478
479enum RunMode { NORMAL, IMPORT, MERGE, BROWSE };
480
481#define ABORTED_BY_USER "aborted by user"
482
483static ARB_ERROR check_argument_for_mode(const char *database, char *&browser_startdir, RunMode& mode) {
484    // Check whether 'database' is a
485    // - ARB database
486    // - directory name
487    // - file to import
488    //
489    // Modify 'mode' occordingly.
490    // Set 'browser_startdir'
491
492    ARB_ERROR error;
493    if (mode != IMPORT) {
494        if (!database) mode = BROWSE;
495        else {
496            GB_ERROR load_file_err = GBT_check_arb_file(database);
497            if (load_file_err) {
498                char *full_path = AW_unfold_path("PWD", database);
499
500                enum Action { LOAD_DB, CONVERT_DB, BROWSE_DB, EXIT, NOIDEA };
501                Action action = NOIDEA;
502                if (GB_is_directory(full_path)) {
503                    action = BROWSE_DB;
504                }
505                else {
506                    if (!GB_is_regularfile(full_path)) {
507                        const char *msg = GBS_global_string("'%s' is neither a known option nor a legal file- or directory-name.\n(Error: %s)",
508                                                            full_path, load_file_err);
509
510                        int ans = aw_question(NULp, msg, "Browser,Exit");
511                        action  = ans ? EXIT : BROWSE_DB;
512                    }
513                    else {
514                        const char *msg = GBS_global_string("Your file is not an original arb file\n(%s)", load_file_err);
515                        action          = (Action)aw_question(NULp, msg, "Continue (dangerous),Start Converter,Browser,Exit");
516                    }
517                }
518
519                switch (action) {
520                    case CONVERT_DB:    mode = IMPORT; break;
521                    case LOAD_DB:       break;
522                    case NOIDEA:        nt_assert(0); FALLTHROUGH; // in NDEBUG
523                    case EXIT:          error = ABORTED_BY_USER; break;
524                    case BROWSE_DB: {
525                        char *dir = nulldup(full_path);
526                        while (dir && !GB_is_directory(dir)) freeset(dir, AW_extract_directory(dir));
527
528                        if (dir) {
529                            nt_assert(GB_is_directory(dir));
530                            reassign(browser_startdir, dir);
531                            mode = BROWSE;
532                        }
533                        free(dir);
534                        break;
535                    }
536               
537                }
538                free(full_path);
539            }
540        }
541    }
542   
543    return error;
544}
545
546class SelectedDatabase : virtual Noncopyable {
547    GBDATA*&  gb_main;
548    char     *name;
549    ArgType   type;
550    char     *role;
551
552    void fix_name() {
553        if (type != RUNNING_DB && name[0]) {
554            const char *changed;
555
556            if (type == NEW_DB) {
557                changed = GB_unfold_in_directory(GB_getcwd(), name);
558            }
559            else {
560                changed = GB_canonical_path(name);
561            }
562
563            if (strcmp(name, changed) != 0) {
564                reselect_file(changed);
565            }
566        }
567    }
568
569public:
570    SelectedDatabase(GBDATA*& gb_main_, const char *name_, const char *role_)
571        : gb_main(gb_main_),
572          name(ARB_strdup(name_)),
573          type(detectArgType(name)),
574          role(ARB_strdup(role_))
575    {
576        fix_name();
577    }
578    ~SelectedDatabase() {
579        free(role);
580        free(name);
581    }
582
583    ArgType arg_type() const { return type; }
584
585    bool needs_to_prompt() const {
586        return type == DIRECTORY || type == FILEMASK || type == UNKNOWN_ARG;
587    }
588
589    const char *get_fullname() const { return name; }
590    const char *get_nameonly() const {
591        const char *slash = strrchr(name, '/');
592        return slash ? slash+1 : name;
593    }
594    const char *get_role() const { return role; }
595    const char *get_description() const { return GBS_global_string("%s (%s)", get_role(), get_nameonly()); }
596
597    ArgType get_type() const { return type; }
598
599    const char *get_dir() const {
600        const char *dir = NULp;
601        switch (type) {
602            case DIRECTORY:
603                dir = get_fullname();
604                break;
605
606            case NEW_DB:
607            case FILEMASK:
608            case EXISTING_FILE:
609            case EXISTING_DB: {
610                char *dir_copy;
611                GB_split_full_path(name, &dir_copy, NULp, NULp, NULp);
612
613                static SmartCharPtr dir_store;
614                dir_store = dir_copy;
615
616                dir = dir_copy;
617                break;
618            }
619            case UNKNOWN_ARG:
620            case RUNNING_DB:
621                dir = ".";
622                break;
623        }
624        return dir;
625    }
626
627    const char *get_mask() const {
628        const char *mask;
629        switch (type) {
630            case FILEMASK: {
631                char *mask_copy;
632                GB_split_full_path(name, NULp, &mask_copy, NULp, NULp);
633
634                static SmartCharPtr mask_store;
635                mask_store = mask_copy;
636
637                mask = mask_copy;
638                break;
639            }
640            default:
641                mask = ".arb";
642                break;
643        }
644        return mask;
645    }
646
647    void reselect_file(const char *file) {
648        freedup(name, file);
649        type = detectArgType(name);
650        fix_name();
651    }
652    void reselect_from_awar(AW_root *aw_root, const char *awar_name) {
653        if (awar_name) {
654            const char *file = aw_root->awar(awar_name)->read_char_pntr();
655            if (file) reselect_file(file);
656        }
657    }
658
659    GB_ERROR open_db_for_merge(bool is_source_db);
660    void close_db() { if (gb_main) GB_close(gb_main); }
661};
662
663GB_ERROR SelectedDatabase::open_db_for_merge(bool is_source_db) {
664    GB_ERROR    error    = NULp;
665    const char *openMode = "rw";
666
667    switch (get_type()) {
668        case DIRECTORY:
669        case FILEMASK:
670            GBK_terminate("Program logic error (should have been prompted for explicit DB name)");
671            break;
672
673        case UNKNOWN_ARG:
674        case EXISTING_FILE:
675            error = GBS_global_string("'%s' is no arb-database", get_fullname());
676            break;
677
678        case NEW_DB:
679            if (is_source_db) {
680                error = GBS_global_string("'%s' has to exist", get_fullname());
681                break;
682            }
683            openMode = "crw"; // allow to create DB
684            break;
685
686        case EXISTING_DB:
687        case RUNNING_DB:
688            break;
689    }
690
691    if (!error) {
692        gb_main             = GBT_open(get_fullname(), openMode);
693        if (!gb_main) error = GB_await_error();
694        else {
695            IF_DEBUG(AWT_announce_db_to_browser(gb_main, get_description()));
696        }
697    }
698
699    return error;
700}
701
702struct merge_scheme : virtual Noncopyable {
703    SelectedDatabase *src;
704    SelectedDatabase *dst;
705
706    char *awar_src;
707    char *awar_dst;
708
709    GB_ERROR error;
710
711    merge_scheme(SelectedDatabase *src_, SelectedDatabase *dst_)
712        : src(src_),
713          dst(dst_),
714          awar_src(NULp),
715          awar_dst(NULp),
716          error(NULp)
717    {}
718    ~merge_scheme() {
719        if (error) {
720            src->close_db();
721            dst->close_db();
722        }
723        delete src;
724        delete dst;
725        free(awar_src);
726        free(awar_dst);
727    }
728
729
730    void open_dbs() {
731        if (!error) error = src->open_db_for_merge(true);
732        if (!error) error = dst->open_db_for_merge(false);
733    }
734
735    void fix_dst(AW_root *aw_root) { dst->reselect_from_awar(aw_root, awar_dst); }
736    void fix_src(AW_root *aw_root) { src->reselect_from_awar(aw_root, awar_src); }
737
738    bool knows_dbs() const { return !src->needs_to_prompt() && !dst->needs_to_prompt(); }
739};
740
741static bool merge_tool_running_as_client = true; // default to safe state (true avoids call of 'arb_clean' at exit)
742static void exit_from_merge(const char *restart_args) {
743    if (merge_tool_running_as_client) { // there is a main ARB running
744        if (restart_args) NT_start(restart_args, true);
745        exit(EXIT_SUCCESS); // exit w/o killing clients (in contrast to NT_exit())
746    }
747    else {
748        NT_restart(AW_root::SINGLETON, null2empty(restart_args));
749        nt_assert(0);
750    }
751}
752
753static void merge_startup_abort_cb(AW_window*) {
754    fputs("Error: merge aborted by user\n", stderr);
755    exit_from_merge(NULp);
756}
757
758static AW_window *merge_startup_error_window(AW_root *aw_root, GB_ERROR error) {
759    AW_window_simple *aw_msg = new AW_window_simple;
760
761    aw_msg->init(aw_root, "arb_merge_error", "ARB merge error");
762    aw_msg->recalc_size_atShow(AW_RESIZE_DEFAULT); // force size recalc (ignores user size)
763
764    aw_msg->at(10, 10);
765    aw_msg->auto_space(10, 10);
766
767    aw_msg->create_autosize_button(NULp, error, "", 2);
768    aw_msg->at_newline();
769
770    aw_msg->callback(merge_startup_abort_cb);
771    aw_msg->create_autosize_button("OK", "Ok", "O", 2);
772
773    aw_msg->window_fit();
774
775    return aw_msg;
776}
777static AW_window *startup_merge_main_window(AW_root *aw_root, merge_scheme *ms) {
778    ms->fix_src(aw_root);
779    ms->fix_dst(aw_root);
780
781    if (ms->knows_dbs()) {
782        ms->open_dbs();
783    }
784    else {
785        ms->error = "Need two databases to merge";
786    }
787
788    AW_window *aw_result;
789    if (!ms->error) {
790        MERGE_create_db_file_awars(aw_root, AW_ROOT_DEFAULT, ms->src->get_fullname(), ms->dst->get_fullname());
791        bool dst_is_new = ms->dst->arg_type() == NEW_DB;
792        aw_result       = MERGE_create_main_window(aw_root, dst_is_new, exit_from_merge);
793
794        nt_assert(got_macro_ability(aw_root));
795    }
796    else {
797        aw_result = merge_startup_error_window(aw_root, ms->error);
798    }
799    delete ms; // was allocated in startup_gui()
800    return aw_result;
801}
802
803static AW_window *startup_merge_prompting_for_nonexplicit_dst_db(AW_root *aw_root, merge_scheme *ms) {
804    AW_window *aw_result;
805    if (ms->dst->needs_to_prompt()) {
806        aw_result = awt_create_load_box(aw_root, "Select", ms->dst->get_role(),
807                                        ms->dst->get_dir(), ms->dst->get_mask(),
808                                        &(ms->awar_dst),
809                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_main_window, ms)),
810                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
811    }
812    else {
813        aw_result = startup_merge_main_window(aw_root, ms);
814    }
815    return aw_result;
816}
817
818static AW_window *startup_merge_prompting_for_nonexplicit_dbs(AW_root *aw_root, merge_scheme *ms) {
819    // if src_spec or dst_spec needs to be prompted for -> startup prompters with callbacks bound to continue
820    // otherwise just startup merge
821
822    AW_window *aw_result;
823    if (ms->src->needs_to_prompt()) {
824        aw_result = awt_create_load_box(aw_root, "Select", ms->src->get_role(),
825                                        ms->src->get_dir(), ms->src->get_mask(),
826                                        &(ms->awar_src),
827                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_prompting_for_nonexplicit_dst_db, ms)),
828                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
829    }
830    else {
831        aw_result = startup_merge_prompting_for_nonexplicit_dst_db(aw_root, ms);
832    }
833    return aw_result;
834}
835
836static void startup_gui(NtreeCommandLine& cl, ARB_ERROR& error) {
837    {
838        char *message = ARB_strdup(GB_path_in_ARBLIB("message"));
839        char *stamp   = ARB_strdup(GB_path_in_arbprop("msgtime"));
840        if (GB_time_of_file(message)>GB_time_of_file(stamp)) {
841            AW_edit(message);
842            AWT_system_cb(GBS_global_string("touch %s", stamp));
843        }
844        free(stamp);
845        free(message);
846    }
847
848    // create some early awars
849    // Note: normally you don't like to add your awar-init-function here, but into nt_create_all_awars()
850
851    AW_root* aw_root = GLOBAL.aw_root;
852
853    AW_create_fileselection_awars(aw_root, AWAR_DBBASE, "", ".arb", "noname.arb");
854    aw_root->awar_string(AWAR_DB_NAME, "noname.arb");
855    aw_root->awar(AWAR_DB_PATH)->add_callback(AWAR_DB_PATH_changed_cb);
856
857    aw_root->awar_int(AWAR_EXPERT, 0);
858
859    AWT_install_cb_guards();
860
861    ARB_declare_global_awars(aw_root, AW_ROOT_DEFAULT);
862
863    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside prompters that may popup
864    if (!error) {
865        nt_assert(!cl.wants_help());
866               
867        RunMode mode = NORMAL;
868
869        if      (cl.wants_merge())  mode = MERGE;
870        else if (cl.wants_import()) mode = IMPORT;
871
872        if (mode == MERGE) {
873            MERGE_create_all_awars(aw_root, AW_ROOT_DEFAULT);
874
875            merge_scheme *ms = new merge_scheme( // freed in startup_merge_main_window()
876                new SelectedDatabase(GLOBAL_gb_src, cl.get_arg(0), "Source DB for merge"),
877                new SelectedDatabase(GLOBAL_gb_dst, cl.get_arg(1), "Destination DB for merge"));
878
879            merge_tool_running_as_client = ms->src->arg_type() == RUNNING_DB || ms->dst->arg_type() == RUNNING_DB;
880
881            if (ms->src->arg_type() == RUNNING_DB && ms->dst->arg_type() == RUNNING_DB) {
882                error = "You cannot merge from running to running DB";
883            }
884            else {
885                aw_root->setUserActionTracker(new NullTracker); // no macro recording during startup of merge tool (file prompts)
886                AW_window *aww = startup_merge_prompting_for_nonexplicit_dbs(aw_root, ms);
887
888                nt_assert(contradicted(aww, error));
889
890                if (!error) {
891                    aww->show();
892                    aw_root->main_loop();
893                    nt_assert(0);
894                }
895            }
896        }
897        else {
898            const char *database         = NULp;
899            char       *browser_startdir = ARB_strdup(".");
900
901            if (cl.free_args() > 0) database = cl.get_arg(0);
902
903            error = check_argument_for_mode(database, browser_startdir, mode);
904            if (!error) {
905                if (mode == IMPORT) {
906                    AWTI_open_import_window(aw_root, database, true, NULp, makeRootCallback(start_main_window_after_import));
907                    AWTI_set_importDB_pointer(GLOBAL.gb_main);
908
909                    nt_assert(got_macro_ability(aw_root));
910                    aw_root->main_loop();
911                }
912                else if (mode == NORMAL) {
913                    aw_root->awar(AWAR_DB_PATH)->write_string(database);
914                    error = load_and_startup_main_window(aw_root, cl.autorun_macro());
915                    if (!error) {
916                        nt_assert(got_macro_ability(aw_root));
917                        aw_root->main_loop();
918                    }
919                }
920                else if (mode == BROWSE) {
921                    aw_root->awar(AWAR_DB_DIRECTORY)->write_string(browser_startdir);
922                    char *latest = GB_find_latest_file(browser_startdir, "/\\.(arb|a[0-9]{2})$/");
923                    if (latest) {
924                        int l = strlen(latest);
925                        strcpy(latest+l-3, "arb");
926                        aw_root->awar(AWAR_DB_PATH)->write_string(latest);
927                        free(latest);
928                    }
929                    nt_create_intro_window(aw_root)->show();
930                    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside intro window
931                    aw_root->main_loop();
932                }
933            }
934            free(browser_startdir);
935        }
936    }
937
938    if (error) {
939        const char *msg = error.preserve();
940        if (strcmp(msg, ABORTED_BY_USER) != 0) aw_popup_ok(msg);
941    }
942}
943
944int ARB_main(int argc, char *argv[]) {
945    {
946        // i really dont want 'arb_ntree --help' to startup the GUI
947        // hack: parse CL twice
948        NtreeCommandLine cl(argc, argv);
949        bool             cl_ok = !cl.parse().deliver();
950        if (cl_ok) {
951            if (cl.wants_help()) {
952                cl.print_help(stderr);
953                return EXIT_SUCCESS;
954            }
955        }
956    }
957
958    aw_initstatus();
959    GB_set_verbose();
960
961    GB_shell shell;
962    AW_root *aw_root = AWT_create_root("ntree.arb", "ARB_NT", need_macro_ability());
963
964    GLOBAL.aw_root = aw_root;
965
966    NtreeCommandLine cl(argc, argv);
967    ARB_ERROR        error = cl.parse();
968
969    if (!error) {
970        if (cl.wants_help()) {
971            cl.print_help(stderr);
972        }
973        else {
974            ModRLimit increase_stacksize(RLIMIT_STACK, TREEDISP_STACKSIZE);
975            startup_gui(cl, error);
976        }
977    }
978
979    int exitcode = EXIT_SUCCESS;
980    if (error) {
981        exitcode = EXIT_FAILURE;
982        fprintf(stderr, "arb_ntree: Error: %s\n", error.deliver());
983    }
984    else {
985        error.expect_no_error();
986    }
987
988    delete aw_root;
989
990    return exitcode;
991}
992
Note: See TracBrowser for help on using the repository browser.