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