Saving Configuration Information

libgnome comes with the ability to store simple key-value pairs in plain text configuration files. Convenience routines are provided for numeric and boolean types which transparently convert to and from a text representation of each type. The standard location for Gnome configuration files is ~/.gnome, and the library will use that location by default. However, the library can be used with any file. There are also variants of each function which save to ~/.gnome_private, a directory with user permissions only. The basic functions to store and retrieve data are listed in Figure 4 in the section called Reading Stored Config Data and Figure 5 in the section called Storing Data In Configuration Files. This module of libgnome is often referred to as gnome-config. Don't confuse this usage of "gnome-config" with the gnome-config script that reports the compile and link flags for Gnome programs.

The gnome-config functions work with a path. A path has three components:

A path is passed to Gnome as a string, with the form "/filename/section/key". If you want to use a filename which is not in the standard Gnome directories, you can bracket the entire path with the '=' character and it will be interpreted as absolute. You can even use this as a simple datafile format (it is used for the .desktop files programs install in order to appear on the Gnome panel menu). However, XML (perhaps using the gnome-xml package) is almost certainly a better choice for that. XML may also be a better choice for storing some kinds of configuration information; the primary advantage of the libgnome configuration library is its simplicity.

gnome-config has a long history; it was first written for the WINE Windows emulator project, then used in the GNU Midnight Commander file manager, and finally migrated into the Gnome libraries. The plan is to replace gnome-config with something more powerful in the next version of Gnome; we want to support per-host configuration, backends such as LDAP, and other features. However, the gnome-config API will almost certainly be supported even if the underlying engine changes dramatically.

Reading Stored Config Data

Retrieving data from files is simple. You simply call a function to retrieve the value for a given key. The value-retrieving functions (shown in Figure 4) accept a path as their argument. For example, you might ask whether the user wants to see a dialog box:


  gboolean show_dialog;

  show_dialog = 
    gnome_config_get_bool("/myapp/General/dialog"); 

If the config file doesn't exist yet, or there is no key matching the path you provide, these functions return 0, FALSE, or NULL. The functions that return a string return allocated memory; you should g_free() the returned string. The string vector functions return an allocated vector full of allocated strings (g_strfreev() is the easiest way to free this vector).

You can specify a default value to be returned if the key does not exist; to do so, append an "=value" to the path. For example:


  gboolean show_dialog;

  show_dialog = 
    gnome_config_get_bool("/myapp/General/dialog=true"); 

Each function has a with_default variant; these tell you whether the return value was taken from a config file or from the default you specified. For example:


  gboolean show_dialog;
  gboolean used_default;

  show_dialog = 
    gnome_config_get_bool_with_default("/myapp/General/dialog=true", 
                                       &used_default); 

  if (used_default)
    printf("Default value used for show_dialog\n");

gnome_config_push_prefix() and gnome_config_pop_prefix() (in Figure 7 in the section called Other Config File Operations) can be used to avoid specifying the entire path each time. For example:


  gboolean show_dialog;

  gnome_config_push_prefix("/myapp/General/");

  show_dialog = 
    gnome_config_get_bool("dialog=true"); 

  gnome_config_pop_prefix();

These functions also work when saving values.

The configuration functions with private in their name use a .gnome_private directory with restricted permissions, as discussed above. The translated_string functions qualify the provided key with the name of the current locale; these are used when Gnome reads .desktop files (see the section called .desktop Entries in the chapter called Creating Your Source Tree) and are probably not useful to applications.

#include <libgnome/gnome-config.h>

gchar* gnome_config_get_string(const gchar* path);

gchar* gnome_config_get_translated_string(const gchar* path);

gint gnome_config_get_int(const gchar* path);

gdouble gnome_config_get_float(const gchar* path);

gboolean gnome_config_get_bool(const gchar* path);

void gnome_config_get_vector(const gchar* path, gint* argcp, gchar*** argvp);

gchar* gnome_config_private_get_string(const gchar* path);

gchar* gnome_config_private_get_translated_string(const gchar* path);

gint gnome_config_private_get_int(const gchar* path);

gdouble gnome_config_private_get_float(const gchar* path);

gboolean gnome_config_private_get_bool(const gchar* path);

void gnome_config_private_get_vector(const gchar* path, gint* argcp, gchar*** argvp);

gchar* gnome_config_get_string_with_default(const gchar* path, gboolean* was_default);

gchar* gnome_config_get_translated_string_with_default(const gchar* path, gboolean* was_default);

gint gnome_config_get_int_with_default(const gchar* path, gboolean* was_default);

gdouble gnome_config_get_float_with_default(const gchar* path, gboolean* was_default);

gboolean gnome_config_get_bool_with_default(const gchar* path, gboolean* was_default);

void gnome_config_get_vector_with_default(const gchar* path, gint* argcp, gchar*** argvp, gboolean* was_default);

gchar* gnome_config_private_get_string_with_default(const gchar* path, gboolean* was_default);

gchar* gnome_config_private_get_translated_string_with_default(const gchar* path, gboolean* was_default);

gint gnome_config_private_get_int_with_default(const gchar* path, gboolean* was_default);

gdouble gnome_config_private_get_float_with_default(const gchar* path, gboolean* was_default);

gboolean gnome_config_private_get_bool_with_default(const gchar* path, gboolean* was_default);

void gnome_config_private_get_vector_with_default(const gchar* path, gint* argcp, gchar*** argvp, gboolean* was_default);

Figure 4. Retrieving data from configuration files

Storing Data In Configuration Files

Saving data is simply the inverse of loading it; you provide a "/file/section/key" path in just the same way, along with the value to store. Data is not written immediately; you must call gnome_config_sync() to ensure the file is written to disk.

#include <libgnome/gnome-config.h>

void gnome_config_set_string(const gchar* path, const gchar* value);

void gnome_config_set_translated_string(const gchar* path, const gchar* value);

void gnome_config_set_int(const gchar* path, gint value);

void gnome_config_set_float(const gchar* path, gdouble value);

void gnome_config_set_bool(const gchar* path, gboolean value);

void gnome_config_set_vector(const gchar* path, gint argc, const gchar* const argv[]);

void gnome_config_private_set_string(const gchar* path, const gchar* value);

void gnome_config_private_set_translated_string(const gchar* path, const gchar* value);

void gnome_config_private_set_int(const gchar* path, gint value);

void gnome_config_private_set_float(const gchar* path, gdouble value);

void gnome_config_private_set_bool(const gchar* path, gboolean value);

void gnome_config_private_set_vector(const gchar* path, gint argc, const gchar* const argv[]);

Figure 5. Saving data to configuration files

Config File Iterators

Iterators are used to scan the sections in a given file, or the keys in a given section. Applications can use this feature to store lists of data, by dynamically generating key or section names to save and later iterating over them to discover what was saved. The functions are summarized in Figure 6 in the section called Section Iterators.

An iterator is an opaque data type; you pass gnome_config_init_iterator() the name of a section to iterate over and receive an iterator in return. You then call gnome_config_iterator_next() to obtain key-value pairs from the section. The key and value returned from gnome_config_iterator_next() must be freed with g_free(), and the return value of gnome_config_iterator_next() is a pointer to the next iterator. When gnome_config_iterator_next() returns NULL, all key-value pairs have been traversed.

Iteration Example from gnome-apt

Here's an example from gnome-apt, a C++ application used to manage packages on Debian systems. gnome-apt loads and saves the position of some columns in a tree display. The columns are identified by the GAptPkgTree::ColumnType enumeration. GAptPkgTree::ColumnTypeEnd is the last element in the column type enumeration, and equal to the number of valid column types. This example is frighteningly "real world" and checks for a number of error conditions.


static void
load_column_order(vector<GAptPkgTree::ColumnType> & columns)
{
  gpointer config_iterator;
  guint loaded = 0;

  config_iterator = gnome_config_init_iterator("/gnome-apt/ColumnOrder");

  if (config_iterator != 0)
    {
      gchar * col, * pos;
      columns.reserve(GAptPkgTree::ColumnTypeEnd);
      
      loaded = 0;
      while ((config_iterator = 
              gnome_config_iterator_next(config_iterator, 
                                         &col, &pos))) 
        {
          // shouldn't happen, but I'm paranoid
          if (pos == 0 || col == 0)
            {
              if (pos) g_free(pos);
              if (col) g_free(col);
              continue;
            }
          
          GAptPkgTree::ColumnType ct = string_to_column(col);
          
          gint index = atoi(pos);

          g_free(pos); pos = 0;
          g_free(col); col = 0;
          
          // the user could mangle the config file to make this happen
          if (static_cast<guint>(index) >= columns.size()) 
            continue;
          
          columns[index] = ct;
          
          ++loaded;
        }
    }

  if (loaded != static_cast<guint>(GAptPkgTree::ColumnTypeEnd))
    {
      // Either there was no saved order, or something is busted - use
      // default order
      columns.clear();
      
      int i = 0;
      while (i < GAptPkgTree::ColumnTypeEnd)
        {
          columns.push_back(static_cast<GAptPkgTree::ColumnType>(i));
          ++i;
        }

      // Clean the section - otherwise an old entry could 
      //  remain forever and keep screwing us up in the future.
      gnome_config_clean_section("/gnome-apt/ColumnOrder");
      gnome_config_sync();
    }
  
  g_return_if_fail(columns.size() == 
                   static_cast<guint>(GAptPkgTree::ColumnTypeEnd));
}

It might be helpful to see the function that saves the column positions:


static void
save_column_order(const vector<GAptPkgTree::ColumnType> & columns)
{
  g_return_if_fail(columns.size() == 
                   static_cast<guint>(GAptPkgTree::ColumnTypeEnd));

  int position = 0;
  vector<GAptPkgTree::ColumnType>::const_iterator i = columns.begin();
  while (i != columns.end())
    {
      gchar key[256];
      g_snprintf(key, 255, "/gnome-apt/ColumnOrder/%s", column_to_string(*i));
      gchar val[30];
      g_snprintf(val, 29, "%d", position);
      gnome_config_set_string(key, val);
      
      ++position;
      ++i;
    }

  gnome_config_sync();
}

When writing this code, the decision was made to store enumeration values as strings rather than integers. The column_to_string() and string_to_column() functions use a simple array of column names indexed by the enumeration values to convert back and forth. There are two reasons to do this: it will not break when the enumeration is altered in future versions of the program, and it keeps the configuration file human-editable.

You may also notice that the column positions are stored with gnome_config_set_string() instead of gnome_config_set_int(). This is because gnome_config_iterator_next() returns a string representation of the stored information, as found in the file. Most likely, gnome_config_set_int() stores integers as strings atoi() would understand (in fact it does), but it is technically not guaranteed by the API. If the code used gnome_config_set_int(), it would have to obtain only the key from gnome_config_iterator_next() and then call gnome_config_get_int() to obtain the integer value. Using atoi() on the string value would make unwarranted assumptions about gnome-config's implementation.

Section Iterators

gnome_config_init_iterator_sections() allows you to iterate over the sections in a file, rather than over the keys in a section. When iterating over sections, gnome_config_iterator_next() ignores its value argument and places the section name in the key argument.

#include <libgnome/gnome-config.h>

void* gnome_config_init_iterator(const gchar* path);

void* gnome_config_private_init_iterator(const gchar* path);

void* gnome_config_init_iterator_sections(const gchar* path);

void* gnome_config_private_init_iterator_sections(const gchar* path);

void* gnome_config_iterator_next(void* iterator_handle, gchar** key, gchar** value);

Figure 6. Configuration file iterators

Other Config File Operations

Figure 7 lists some additional operations available for manipulating config files. The most important of these have already been mentioned in passing. gnome_config_sync() writes the configuration file to disk, and gnome_config_push_prefix() allows you to shorten the path passed to the other gnome-config functions. There are also boolean tests, to ask gnome-config whether a given section exists.

Two new operations are introduced: to drop a file or section means to forget any information about it stored in memory, including cached values loaded from the file and values not yet saved to the file with gnome_config_sync(). To clean a file, section, or key means to unset its value(s), so the file, section, or key will not exist once gnome_config_sync() is called.

gnome_config_sync() automatically calls gnome_config_drop_all() to free all gnome-config resources, since the information is safely stored on disk.

Functions are also provided to get the "real" (filesystem) path of a configuration file from a gnome-config path. These are unlikely to be useful in application code.

#include <libgnome/gnome-config.h>

gboolean gnome_config_has_section(const gchar* path);

gboolean gnome_config_private_has_section(const gchar* path);

void gnome_config_drop_all(void);

void gnome_config_sync(void);

void gnome_config_sync_file(const gchar* path);

void gnome_config_private_sync_file(const gchar* path);

void gnome_config_drop_file(const gchar* path);

void gnome_config_private_drop_file(const gchar* path);

void gnome_config_clean_file(const gchar* path);

void gnome_config_private_clean_file(const gchar* path);

void gnome_config_clean_section(const gchar* path);

void gnome_config_private_clean_section(const gchar* path);

void gnome_config_clean_key(const gchar* path);

void gnome_config_private_clean_key(const gchar* path);

gchar* gnome_config_get_real_path(const gchar* path);

gchar* gnome_config_private_get_real_path(const gchar* path);

void gnome_config_push_prefix(const gchar* path);

void gnome_config_pop_prefix(void);

Figure 7. Miscellaneous configuration file functions