source: tags/arb-6.0/AWT/AWT_config_manager.cxx

Last change on this file was 12267, checked in by westram, 10 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.8 KB
Line 
1//  ==================================================================== //
2//                                                                       //
3//    File      : AWT_config_manager.cxx                                 //
4//    Purpose   :                                                        //
5//                                                                       //
6//                                                                       //
7//  Coded by Ralf Westram (coder@reallysoft.de) in January 2002          //
8//  Copyright Department of Microbiology (Technical University Munich)   //
9//                                                                       //
10//  Visit our web site at: http://www.arb-home.de/                       //
11//                                                                       //
12//                                                                       //
13//  ==================================================================== //
14
15#include "awt_config_manager.hxx"
16#include "awt.hxx"
17#include <arbdb.h>
18#include <aw_window.hxx>
19#include <aw_root.hxx>
20#include <aw_question.hxx>
21#include <aw_awar.hxx>
22#include <aw_msg.hxx>
23#include <arb_strarray.h>
24
25#include <map>
26#include <string>
27
28using namespace std;
29
30// --------------------------
31//      AWT_configuration
32
33class AWT_configuration : virtual Noncopyable {
34private:
35    string id;
36
37    AWT_store_config_to_string  store;
38    AWT_load_config_from_string load;
39    AW_CL                       client1; // client data
40    AW_CL                       client2;
41
42    AW_window  *last_client_aww;
43    AW_default  default_file;
44
45public:
46    AWT_configuration(AW_window *aww, AW_default default_file_, const char *id_, AWT_store_config_to_string store_, AWT_load_config_from_string load_, AW_CL cl1, AW_CL cl2)
47        : id(id_),
48          store(store_),
49          load(load_),
50          client1(cl1),
51          client2(cl2),
52          last_client_aww(aww),
53          default_file(default_file_)
54    {}
55    virtual ~AWT_configuration() {}
56
57    bool operator<(const AWT_configuration& other) const { return id<other.id; }
58    string get_awar_name(const string& subname) const { return string("general_configs/")+id+'/'+subname; }
59    string get_awar_value(const string& subname, const char *default_value = "") const {
60        AW_root *aw_root   = last_client_aww->get_root();
61        string   awar_name = get_awar_name(subname);
62        char    *value     = aw_root->awar_string(awar_name.c_str(), default_value, default_file)->read_string();
63        string   result    = value;
64        free(value);
65        return result;
66    }
67    void set_awar_value(const string& subname, const string& new_value) const {
68        AW_root *aw_root   = last_client_aww->get_root();
69        aw_root->awar_string(get_awar_name(subname).c_str(), "")->write_string(new_value.c_str());
70    }
71
72    const char *get_id() const { return id.c_str(); }
73
74    char *Store() const { return store(last_client_aww, client1, client2); }
75    GB_ERROR Restore(const string& s) const {
76        GB_ERROR error = 0;
77
78        if (s.empty()) error = "empty/nonexistant config";
79        else load(last_client_aww, s.c_str(), client1, client2);
80
81        return error;
82    }
83
84    GB_ERROR Save(const char* filename, const string& awar_name); // AWAR content -> FILE
85    GB_ERROR Load(const char* filename, const string& awar_name); // FILE -> AWAR content
86};
87
88#define HEADER    "ARB_CONFIGURATION"
89#define HEADERLEN 17
90
91GB_ERROR AWT_configuration::Save(const char* filename, const string& awar_name) {
92    awt_assert(strlen(HEADER) == HEADERLEN);
93
94    printf("Saving config to '%s'..\n", filename);
95
96    FILE     *out   = fopen(filename, "wt");
97    GB_ERROR  error = 0;
98    if (!out) {
99        error = GB_export_IO_error("saving", filename);
100    }
101    else {
102        fprintf(out, HEADER ":%s\n", id.c_str());
103        string content = get_awar_value(awar_name);
104        fputs(content.c_str(), out);
105        fclose(out);
106    }
107    return error;
108}
109
110GB_ERROR AWT_configuration::Load(const char* filename, const string& awar_name) {
111    GB_ERROR error = 0;
112
113    printf("Loading config from '%s'..\n", filename);
114
115    char *content = GB_read_file(filename);
116    if (!content) {
117        error = GB_await_error();
118    }
119    else {
120        if (strncmp(content, HEADER ":", HEADERLEN+1) != 0) {
121            error = "Unexpected content (" HEADER " missing)";
122        }
123        else {
124            char *id_pos = content+HEADERLEN+1;
125            char *nl     = strchr(id_pos, '\n');
126
127            if (!nl) {
128                error = "Unexpected content (no ID)";
129            }
130            else {
131                *nl++ = 0;
132                if (strcmp(id_pos, id.c_str()) != 0) {
133                    error = GBS_global_string("Wrong config (id=%s, expected=%s)", id_pos, id.c_str());
134                }
135                else {
136                    set_awar_value(awar_name, nl);
137                }
138            }
139        }
140
141        if (error) {
142            error = GBS_global_string("Error: %s (while reading %s)", error, filename);
143        }
144
145        free(content);
146    }
147
148    return error;
149}
150
151void remove_from_configs(const string& config, string& existing_configs) {
152    ConstStrArray cfgs;
153    GBT_split_string(cfgs, existing_configs.c_str(), ';');
154
155    ConstStrArray remaining;
156    for (int i = 0; cfgs[i]; ++i) {
157        if (strcmp(cfgs[i], config.c_str()) != 0) {
158            remaining.put(cfgs[i]);
159        }
160    }
161
162    char *rest       = GBT_join_names(remaining, ';');
163    existing_configs = rest;
164    free(rest);
165}
166
167static string config_prefix = "cfg_";
168
169static char *correct_key_name(const char *name) {
170    string  with_prefix = config_prefix+name;
171    char   *corrected   = GBS_string_2_key(with_prefix.c_str());
172    freedup(corrected, strdup(corrected+config_prefix.length())); // remove prefix
173    return corrected;
174}
175
176// ---------------------------------
177//      AWT_start_config_manager
178
179static void AWT_start_config_manager(AW_window *aww, AWT_configuration *config) {
180    string existing_configs = config->get_awar_value("existing");
181    config->get_awar_value("current"); // create!
182
183    bool  reopen = false;
184    char *title  = GBS_global_string_copy("Configurations for '%s'", aww->window_name);
185
186    const char *buttons = "\nRESTORE,STORE,DELETE,LOAD,SAVE,-\nCLOSE,HELP";
187    enum Answer { CM_RESTORE, CM_STORE, CM_DELETE, CM_LOAD, CM_SAVE, CM_none, CM_HELP };
188
189    char   *cfgName = aw_string_selection2awar(title, "Enter a new or select an existing config", config->get_awar_name("current").c_str(), existing_configs.c_str(), buttons);
190    Answer  button  = (Answer)aw_string_selection_button();
191
192    GB_ERROR error = NULL;
193    if (button >= CM_RESTORE && button <= CM_SAVE) {
194        if (!cfgName || !cfgName[0]) {                // did user specify a config-name ?
195            error = "Please enter or select a config";
196        }
197    }
198
199    if (!error && button>=0) {
200        if (button == CM_HELP) {
201            AW_help_popup(aww, "configurations.hlp");
202        }
203        else {
204            freeset(cfgName, correct_key_name(cfgName));
205
206            string  awar_name = config_prefix+cfgName;
207            char   *filename  = 0;
208            bool    loading   = false;
209
210            awt_assert(GB_check_key(awar_name.c_str()) == NULL);
211
212            reopen = true;
213
214            switch (button) {
215                case CM_LOAD: {
216                    char *loadMask = GBS_global_string_copy("%s_*", config->get_id());
217                    filename       = aw_file_selection("Load config from file", "$(ARBCONFIG)", loadMask, ".arbcfg");
218                    free(loadMask);
219                    if (!filename) break;
220
221                    error = config->Load(filename, awar_name);
222                    if (error) break;
223
224                    loading = true;
225                    // fall-through
226                }
227
228                case CM_RESTORE:
229                    error = config->Restore(config->get_awar_value(awar_name));
230                    if (!error) {
231                        config->set_awar_value("current", cfgName);
232                        if (loading) goto AUTOSTORE;
233                        reopen = false;
234                    }
235                    break;
236
237                case CM_SAVE: {
238                    char *saveAs = GBS_global_string_copy("%s_%s", config->get_id(), cfgName);
239                    filename     = aw_file_selection("Save config to file", "$(ARBCONFIG)", saveAs, ".arbcfg");
240                    free(saveAs);
241                    if (!filename) break;
242                    // fall-through
243                }
244                case CM_STORE: {
245                      AUTOSTORE :
246                    remove_from_configs(cfgName, existing_configs); // remove existing config
247
248                    if (existing_configs.length()) existing_configs = string(cfgName)+';'+existing_configs;
249                    else existing_configs                           = cfgName;
250
251                    {
252                        char *config_string = config->Store();
253                        config->set_awar_value(awar_name, config_string);
254                        free(config_string);
255                    }
256                    config->set_awar_value("current", cfgName);
257                    config->set_awar_value("existing", existing_configs);
258
259                    if (filename && !loading) error = config->Save(filename, awar_name); // CM_SAVE
260                    break;
261                }
262
263                case CM_DELETE:
264                    remove_from_configs(cfgName, existing_configs); // remove existing config
265                    config->set_awar_value("current", "");
266                    config->set_awar_value("existing", existing_configs);
267
268                    // @@@ config is not really deleted from properties
269                    break;
270
271                case CM_HELP:
272                case CM_none:
273                    awt_assert(0); // should not come here
274                    break;
275            }
276        }
277    }
278
279    free(title);
280    free(cfgName);
281
282    if (error) {
283        aw_message(error);
284        reopen = true;
285    }
286    if (reopen) AWT_start_config_manager(aww, config);
287}
288
289void AWT_insert_config_manager(AW_window *aww, AW_default default_file_, const char *id, AWT_store_config_to_string store_cb,
290                               AWT_load_config_from_string load_cb, AW_CL cl1, AW_CL cl2, const char *macro_id)
291{
292    AWT_configuration *config = new AWT_configuration(aww, default_file_, id, store_cb, load_cb, cl1, cl2);
293    // config will not be freed!!!
294
295    aww->button_length(0); // -> autodetect size by size of graphic
296    aww->callback(makeWindowCallback(AWT_start_config_manager, config));
297    aww->create_button(macro_id ? macro_id : "SAVELOAD_CONFIG", "#conf_save.xpm");
298}
299
300static GB_ERROR decode_escapes(string& s) {
301    string::iterator f = s.begin();
302    string::iterator t = s.begin();
303
304    for (; f != s.end(); ++f, ++t) {
305        if (*f == '\\') {
306            ++f;
307            if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
308            switch (*f) {
309                case 'n':
310                    *t = '\n';
311                    break;
312                case 'r':
313                    *t = '\r';
314                    break;
315                case 't':
316                    *t = '\t';
317                    break;
318                default:
319                    *t = *f;
320                    break;
321            }
322        }
323        else {
324            *t = *f;
325        }
326    }
327
328    s.erase(t, f);
329
330    return 0;
331}
332
333static void encode_escapes(string& s, const char *to_escape) {
334    string neu;
335    neu.reserve(s.length()*2+1);
336
337    for (string::iterator p = s.begin(); p != s.end(); ++p) {
338        if (*p == '\\' || strchr(to_escape, *p) != 0) {
339            neu = neu+'\\'+*p;
340        }
341        else if (*p == '\n') { neu = neu+"\\n"; }
342        else if (*p == '\r') { neu = neu+"\\r"; }
343        else if (*p == '\t') { neu = neu+"\\t"; }
344        else { neu = neu+*p; }
345    }
346    s = neu;
347}
348
349typedef map<string, string> config_map;
350struct AWT_config_mapping {
351    config_map cmap;
352
353    config_map::iterator entry(const string &e) { return cmap.find(e); }
354
355    config_map::iterator begin() { return cmap.begin(); }
356    config_map::const_iterator end() const { return cmap.end(); }
357    config_map::iterator end() { return cmap.end(); }
358};
359
360// -------------------
361//      AWT_config
362
363AWT_config::AWT_config(const char *config_char_ptr)
364    : mapping(new AWT_config_mapping)
365    , parse_error(0)
366{
367    // parse string in format "key1='value1';key2='value2'"..
368    // and put values into a map.
369    // assumes that keys are unique
370
371    string      configString(config_char_ptr);
372    config_map& cmap  = mapping->cmap;
373    size_t      pos   = 0;
374
375    while (!parse_error) {
376        size_t equal = configString.find('=', pos);
377        if (equal == string::npos) break;
378
379        if (configString[equal+1] != '\'') {
380            parse_error = "expected quote \"'\"";
381            break;
382        }
383        size_t start = equal+2;
384        size_t end   = configString.find('\'', start);
385        while (end != string::npos) {
386            if (configString[end-1] != '\\') break;
387            end = configString.find('\'', end+1);
388        }
389        if (end == string::npos) {
390            parse_error = "could not find matching quote \"'\"";
391            break;
392        }
393
394        string config_name = configString.substr(pos, equal-pos);
395        string value       = configString.substr(start, end-start);
396
397        parse_error = decode_escapes(value);
398        if (!parse_error) {
399            cmap[config_name] = value;
400        }
401
402        pos = end+2;            // skip ';'
403    }
404}
405AWT_config::AWT_config(const AWT_config_mapping *cfgname_2_awar, AW_root *root)
406    : mapping(new AWT_config_mapping)
407    , parse_error(0)
408{
409    const config_map& awarmap  = cfgname_2_awar->cmap;
410    config_map& valuemap = mapping->cmap;
411
412    for (config_map::const_iterator c = awarmap.begin(); c != awarmap.end(); ++c) {
413        const string& key(c->first);
414        const string& awar_name(c->second);
415
416        char *awar_value = root->awar(awar_name.c_str())->read_as_string();
417        valuemap[key]    = awar_value;
418        free(awar_value);
419    }
420
421    awt_assert(valuemap.size() == awarmap.size());
422}
423
424AWT_config::~AWT_config() {
425    delete mapping;
426}
427
428bool AWT_config::has_entry(const char *entry) const {
429    awt_assert(!parse_error);
430    return mapping->entry(entry) != mapping->end();
431}
432const char *AWT_config::get_entry(const char *entry) const {
433    awt_assert(!parse_error);
434    config_map::iterator found = mapping->entry(entry);
435    return (found == mapping->end()) ? 0 : found->second.c_str();
436}
437void AWT_config::set_entry(const char *entry, const char *value) {
438    awt_assert(!parse_error);
439    mapping->cmap[entry] = value;
440}
441void AWT_config::delete_entry(const char *entry) {
442    awt_assert(!parse_error);
443    mapping->cmap.erase(entry);
444}
445
446char *AWT_config::config_string() const {
447    awt_assert(!parse_error);
448    string result;
449    for (config_map::iterator e = mapping->begin(); e != mapping->end(); ++e) {
450        const string& config_name(e->first);
451        string        value(e->second);
452
453        encode_escapes(value, "\'");
454        string entry = config_name+"='"+value+'\'';
455        if (result.empty()) {
456            result = entry;
457        }
458        else {
459            result = result+';'+entry;
460        }
461    }
462    return strdup(result.c_str());
463}
464GB_ERROR AWT_config::write_to_awars(const AWT_config_mapping *cfgname_2_awar, AW_root *root) const {
465    GB_ERROR       error = 0;
466    GB_transaction ta(AW_ROOT_DEFAULT);
467    // Notes:
468    // * Opening a TA on AW_ROOT_DEFAULT has no effect, as awar-DB is TA-free and each
469    //   awar-change opens a TA anyway.
470    // * Motif version did open TA on awar->gb_var (in 1st loop), which would make a
471    //   difference IF the 1st awar is bound to main-DB. At best old behavior was obscure.
472
473    awt_assert(!parse_error);
474    for (config_map::iterator e = mapping->begin(); !error && e != mapping->end(); ++e) {
475        const string& config_name(e->first);
476        const string& value(e->second);
477
478        config_map::const_iterator found = cfgname_2_awar->cmap.find(config_name);
479        if (found == cfgname_2_awar->end()) {
480            error = GBS_global_string("config contains unmapped entry '%s'", config_name.c_str());
481        }
482        else {
483            const string&  awar_name(found->second);
484            AW_awar       *awar = root->awar(awar_name.c_str());
485            awar->write_as_string(value.c_str());
486        }
487    }
488    return error;
489}
490
491// ------------------------------
492//      AWT_config_definition
493
494AWT_config_definition::AWT_config_definition(AW_root *aw_root)
495    : root(aw_root),  config_mapping(new AWT_config_mapping) {}
496
497AWT_config_definition::AWT_config_definition(AW_root *aw_root, AWT_config_mapping_def *mdef)
498    : root(aw_root),  config_mapping(new AWT_config_mapping)
499{
500    add(mdef);
501}
502
503AWT_config_definition::~AWT_config_definition() {
504    delete config_mapping;
505}
506
507void AWT_config_definition::add(const char *awar_name, const char *config_name) {
508    config_mapping->cmap[config_name] = awar_name;
509}
510void AWT_config_definition::add(const char *awar_name, const char *config_name, int counter) {
511    add(awar_name, GBS_global_string("%s%i", config_name, counter));
512}
513void AWT_config_definition::add(AWT_config_mapping_def *mdef) {
514    while (mdef->awar_name && mdef->config_name) {
515        add(mdef->awar_name, mdef->config_name);
516        mdef++;
517    }
518}
519
520char *AWT_config_definition::read() const {
521    // creates a string from awar values
522
523    AWT_config current_state(config_mapping, root);
524    return current_state.config_string();
525}
526void AWT_config_definition::write(const char *config_char_ptr) const {
527    // write values from string to awars
528    // if the string contains unknown settings, they are silently ignored
529
530    AWT_config wanted_state(config_char_ptr);
531    GB_ERROR   error  = wanted_state.parseError();
532    if (!error) error = wanted_state.write_to_awars(config_mapping, root);
533    if (error) aw_message(GBS_global_string("Error restoring configuration (%s)", error));
534}
535
Note: See TracBrowser for help on using the repository browser.