c5b2edf67ac4f27cdd7965ab3e2e2b4fd9324686
[feisty_meow.git] / nucleus / library / configuration / ini_configurator.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : ini_configurator                                                  *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2000-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "ini_configurator.h"
16 #include "application_configuration.h"
17 #include "variable_tokenizer.h"
18
19 #include <application/windoze_helper.h>
20 #include <basis/astring.h>
21 #include <basis/environment.h>
22 #include <basis/functions.h>
23 #include <basis/mutex.h>
24 #include <basis/utf_conversion.h>
25 #include <filesystem/byte_filer.h>
26 #include <filesystem/directory.h>
27 #include <filesystem/filename.h>
28 #include <structures/static_memory_gremlin.h>
29 #include <structures/string_array.h>
30 #include <structures/string_table.h>
31 #include <structures/symbol_table.h>
32
33 #undef LOG
34 #define LOG(to_print) printf("%s::%s: %s\n", static_class_name(), func, astring(to_print).s())
35
36 using namespace basis;
37 using namespace filesystem;
38 using namespace structures;
39
40 namespace configuration {
41
42 //#define DEBUG_INI_CONFIGURATOR
43   // uncomment for noisy version.
44
45 const int MAXIMUM_LINE_INI_CONFIG = 16384;
46
47 // a default we hope never to see in an ini file.
48 SAFE_STATIC_CONST(astring, ini_configurator::ini_str_fake_default, ("NoTomatoesNorPotatoesNorQuayle"))
49
50 ini_configurator::ini_configurator(const astring &ini_filename,
51       treatment_of_defaults behavior, file_location_default where)
52 : configurator(behavior),
53   _ini_name(new filename),
54 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
55   _parser(new ini_parser("", behavior)),
56 #endif
57   _where(where),
58   _add_spaces(false)
59 {
60   name(ini_filename);  // set name properly.
61 }
62
63 ini_configurator::~ini_configurator()
64 {
65   WHACK(_ini_name);
66 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
67   WHACK(_parser);
68 #endif
69 }
70
71 astring ini_configurator::name() const { return _ini_name->raw(); }
72
73 void ini_configurator::refresh()
74 {
75 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
76   write_ini_file();
77   WHACK(_parser);
78   _parser = new ini_parser("", behavior());
79 #endif
80 }
81
82 void ini_configurator::name(const astring &name)
83 {
84   *_ini_name = name;
85
86   bool use_appdir = true;
87     // true if we should put files where programs start for those filenames
88     // that don't include a directory name.
89   if (_where == OS_DIRECTORY) use_appdir = false;
90   if (_where == ALL_USERS_DIRECTORY) use_appdir = false;
91 #ifdef _MSC_VER
92   use_appdir = true;
93 #endif
94   // we must create the filename if they specified no directory at all.
95   if (!_ini_name->had_directory()) {
96     if (use_appdir) {
97       // this is needed in case there is an ini right with the file; our
98       // standard is to check there first.
99       *_ini_name = filename(application_configuration::application_directory(),
100           _ini_name->basename());
101     } else if (!use_appdir && (_where == ALL_USERS_DIRECTORY) ) {
102       // when the location default is all users, we get that from the
103       // environment.  for the OS dir choice, we leave out the path entirely.
104       directory::make_directory(environment::get("ALLUSERSPROFILE")
105           + "/" + application_configuration::software_product_name());
106       *_ini_name = filename(environment::get("ALLUSERSPROFILE")
107           + "/" + application_configuration::software_product_name(),
108           _ini_name->basename());
109     }
110   }
111 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
112   // read in the file's contents.
113   read_ini_file();
114 #endif
115 }
116
117 void ini_configurator::sections(string_array &list)
118 {
119   list = string_array();
120   // open our ini file directly as a file.
121   byte_filer section8(*_ini_name, "rb");
122   if (!section8.good()) return;  // not a healthy file.
123   astring line_found;
124   // iterate through the lines of the ini file and see if we can't find a
125   // bunch of section names.
126   while (section8.read(line_found, MAXIMUM_LINE_INI_CONFIG) > 0) {
127     // is the line in the format "^[ \t]*\[\([^\]]+\)\].*$" ?
128     // if it is in that format, we add the matched \1 into our list.
129     line_found.strip_white_spaces();
130     if (line_found[0] != '[') continue;  // no opening bracket.  skip line.
131     line_found.zap(0, 0);  // toss opening bracket.
132     int close_brack_indy = line_found.find(']');
133     if (negative(close_brack_indy)) continue;  // no closing bracket.
134     line_found.zap(close_brack_indy, line_found.end());
135     list += line_found;
136   }
137 }
138
139 //hmmm: refactor section_exists to use the sections call, if it's faser?
140 bool ini_configurator::section_exists(const astring &section)
141 {
142 #ifdef _MSC_VER
143   string_table infos;
144   // heavy-weight call here...
145   return get_section(section, infos);
146 #else
147   return _parser->section_exists(section);
148 #endif
149 }
150
151 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
152 void ini_configurator::read_ini_file()
153 {
154 #ifdef DEBUG_INI_CONFIGURATOR
155   FUNCDEF("read_ini_file");
156 #endif
157   _parser->reset("");  // clear out our current contents.
158   byte_filer ini_file;
159   bool open_ret = ini_file.open(*_ini_name, "rb");  // simple reading.
160 #ifdef DEBUG_INI_CONFIGURATOR
161   if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name);
162   if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name);
163 #endif
164   if (!open_ret || !ini_file.good()) {
165     return;  // failure.
166   }
167   int file_size = ini_file.length();  // get the file length.
168   // read the file.
169   astring contents(' ', file_size + 3);
170   int bytes_read = ini_file.read((abyte *)contents.observe(), file_size);
171   contents.zap(bytes_read + 1, contents.end());
172   _parser->reset(contents);
173 }
174
175 void ini_configurator::write_ini_file()
176 {
177 #ifdef DEBUG_INI_CONFIGURATOR
178   FUNCDEF("write_ini_file");
179 #endif
180
181 //hmmm: just set dirty flag and use that for deciding whether to write.
182 //hmmm: future version, have a thread scheduled to write.
183
184   // open filer with new mode for cleaning.
185   byte_filer ini_file;
186   bool open_ret = ini_file.open(*_ini_name, "wb");
187     // open the file for binary read/write and drop previous contents.
188 #ifdef DEBUG_INI_CONFIGURATOR
189   if (!open_ret) LOG(astring("failed to open ini file: ") + *_ini_name);
190   if (!ini_file.good()) LOG(astring("ini file not good: ") + *_ini_name);
191 #endif
192   if (!open_ret || !ini_file.good()) return;  // failure.
193
194   // output table's contents to text.
195   astring text;
196   _parser->restate(text, _add_spaces);
197   ini_file.write((abyte *)text.observe(), text.length());
198 }
199 #endif //UNIX
200
201 bool ini_configurator::delete_section(const astring &section)
202 {
203 #ifdef _MSC_VER
204   return put_profile_string(section, "", ""); 
205 #else
206   // zap the section.
207   bool to_return = _parser->delete_section(section);
208   // schedule the file to write.
209   write_ini_file();
210   return to_return;
211 #endif
212 }
213
214 bool ini_configurator::delete_entry(const astring &section, const astring &ent)
215 {
216 #ifdef _MSC_VER
217   return put_profile_string(section, ent, "");
218 #else
219   // zap the entry.
220   bool to_return = _parser->delete_entry(section, ent);
221   // schedule the file to write.
222   write_ini_file();
223   return to_return;
224 #endif
225 }
226
227 bool ini_configurator::put(const astring &section, const astring &entry,
228     const astring &to_store)
229 {
230   FUNCDEF("put");
231   if (!to_store.length()) return delete_entry(section, entry);
232   else if (!entry.length()) return delete_section(section);
233   else if (!section.length()) return false;
234 #ifdef _MSC_VER
235   return put_profile_string(section, entry, to_store);
236 #else
237   // write the entry.
238   bool to_return = _parser->put(section, entry, to_store);
239   // schedule file write.
240   write_ini_file();
241   return to_return;
242 #endif
243 }
244
245 bool ini_configurator::get(const astring &section, const astring &entry,
246     astring &found)
247 {
248 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
249   return _parser->get(section, entry, found);
250 #else
251   flexichar temp_buffer[MAXIMUM_LINE_INI_CONFIG];
252   temp_buffer[0] = 0;
253   get_profile_string(section, entry, ini_str_fake_default(),
254       temp_buffer, MAXIMUM_LINE_INI_CONFIG - 1);
255   found = from_unicode_temp(temp_buffer);
256   return !(ini_str_fake_default() == found);
257 #endif
258 }
259
260 bool ini_configurator::get_section(const astring &section, string_table &info)
261 {
262   FUNCDEF("get_section");
263 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
264   return _parser->get_section(section, info);
265 #else
266   info.reset();
267   const int buffer_size = 200000;
268
269   flexichar low_buff[buffer_size + 3];
270   int read_len = GetPrivateProfileSection(to_unicode_temp(section.observe()),
271       low_buff, buffer_size - 1, to_unicode_temp(name()));
272   if (!read_len) return false;  // assume the API means there was no section.
273
274   low_buff[read_len] = '\1';  // signal beyond the end of the stuff.
275   low_buff[read_len + 1] = '\0';  // make sure we're still zero terminated.
276
277   bool last_was_nil = false;
278   // this loop replaces all the embedded nils with separators to allow the
279   // variable_tokenizer to retrieve all the strings from the section.
280   for (int i = 0; i < read_len; i++) {
281     if (!low_buff[i] && last_was_nil) {
282       // termination condition; we got two nils in a row.
283       // this is just paranoia; the length should tell us.
284       break;
285     } else if (!low_buff[i]) {
286       low_buff[i] = '\1';  // replace with a separator.
287       last_was_nil = true;
288     } else last_was_nil = false;  // reset the nil flag.
289   }
290
291   // now convert to a simple astring.
292   astring buff = from_unicode_temp(low_buff);
293   int length = buff.length();
294   buff.shrink();
295   variable_tokenizer parser("\1", "=");
296   parser.parse(buff);
297   info = parser.table();
298   return true;
299 #endif
300 }
301
302 bool ini_configurator::put_section(const astring &section,
303     const string_table &info)
304 {
305 #ifdef _MSC_VER
306   variable_tokenizer parser("\1", "=");
307   parser.table() = info;
308   astring flat = parser.text_form();
309   flat += "\1\1";  // add terminating guard.
310   int len = flat.length();
311   for (int i = 0; i < len; i++) {
312     if (flat[i] == '\1') {
313       flat[i] = '\0';
314       if (flat[i+1] == ' ') {
315         // if the space character is next, shift it before the nil to avoid
316         // keys with a preceding space.
317         flat[i] = ' ';
318         flat[i + 1] = '\0';
319       }
320     }
321   }
322   return WritePrivateProfileSection(to_unicode_temp(section),
323       to_unicode_temp(flat), to_unicode_temp(name()));
324 #else
325   // write the section.
326   bool to_return = _parser->put_section(section, info);
327   // schedule file write.
328   write_ini_file();
329   return to_return;
330 #endif
331 }
332
333 #ifdef _MSC_VER
334 bool ini_configurator::put_profile_string(const astring &section,
335     const astring &entry, const astring &to_store)
336 {
337   return bool(WritePrivateProfileString(to_unicode_temp(section),
338       entry.length() ? (flexichar *)to_unicode_temp(entry) : NULL_POINTER,
339       to_store.length() ? (flexichar *)to_unicode_temp(to_store) : NULL_POINTER,
340       to_unicode_temp(name())));
341 }
342
343 void ini_configurator::get_profile_string(const astring &section,
344     const astring &entry, const astring &default_value,
345     flexichar *return_buffer, int buffer_size)
346 {
347   GetPrivateProfileString(section.length() ?
348       (flexichar *)to_unicode_temp(section) : NULL_POINTER,
349       entry.length() ? (flexichar *)to_unicode_temp(entry) : NULL_POINTER,
350       to_unicode_temp(default_value),
351       return_buffer, buffer_size, to_unicode_temp(name()));
352 }
353 #endif
354
355 } //namespace.
356
357