Logo Search packages:      
Sourcecode: pan version File versions  Download package

pan-config.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include <config.h>

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <glib.h>

#include <libxml/parser.h>
#include <libxml/xmlmemory.h>

#include <pan/base/debug.h>
#include <pan/base/log.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/base-prefs.h>
#include <pan/base/util-file.h>

#include <pan/base/pan-config.h>

#define MAX_KEY_LEN 2048

/** 
 ** Internal data structures
 **/

typedef enum
{
      IS_NOTYPE,
      IS_STRING,
      IS_BOOL,
      IS_INT,
      IS_UNSIGNED_LONG,
      PAN_CONFIG_NODE_TYPE_QTY
} PanConfigNodeType;

typedef struct 
{
      PString key;
      PanConfigNodeType   type;
      union
      {
            char     * val_string;
            gboolean   val_bool;
            int        val_int;
            gulong     val_ulong;
      } value;
} PanConfigNode;


/**
 ** Managing the configuration tree
 **/

static GNode       * config_root = NULL;
static gboolean      config_is_dirty = FALSE;

static PanConfigNode *
config_create_empty_node (const PString * key)
{
      PanConfigNode * node = g_new (PanConfigNode, 1);
      node->key = PSTRING_INIT;
      pstring_copy (&node->key, key);
      node->type = IS_NOTYPE;
      node->value.val_int = 0;
      return node;
}

static PanConfigNode *
config_create_node (const PString     * key,
                PanConfigNodeType   type,
                va_list             ap)
{
      PanConfigNode * node = g_new (PanConfigNode, 1);

      g_return_val_if_fail (node != NULL, NULL);
      g_return_val_if_fail (pstring_is_set (key), NULL);

      node->key = PSTRING_INIT;
      pstring_copy (&node->key, key);
      node->type = type;
      
      switch (type)
      {
            case IS_STRING:
                  node->value.val_string = g_strdup (va_arg (ap, char *));
                  break;
            case IS_BOOL:
                  node->value.val_bool = va_arg (ap, gboolean);
                  break;
            case IS_INT:
                  node->value.val_int = va_arg (ap, int);
                  break;
            case IS_UNSIGNED_LONG:
                  node->value.val_ulong = va_arg (ap, gulong);
                  break;
            default:
                  break;
      }

      return node;
}


static gboolean
config_node_deleter (GNode * node, gpointer data)
{
      PanConfigNode * config;

      g_return_val_if_fail (node != NULL, FALSE);
      g_return_val_if_fail (node->data != NULL, FALSE);

      config = (PanConfigNode *) node->data;

      if (config->type == IS_STRING)
            g_free (config->value.val_string);
      pstring_clear (&config->key);
      g_free (config);

      node->data = NULL;

      return FALSE;
}


static void
config_delete_node (GNode * node)
{
      g_return_if_fail (node != NULL);

      g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, 
                       config_node_deleter, NULL);

      g_node_destroy (node);

      if (node == config_root)
            config_root = NULL;

      config_is_dirty = TRUE;
}

static GNode *
config_scan_children (GNode * node, const PString * key)
{
      GNode * child = NULL;

      g_return_val_if_fail (node!=NULL, NULL);
      g_return_val_if_fail (pstring_is_set (key), NULL);

      child = g_node_first_child (node);

      while (child != NULL)
      {
            const PanConfigNode * data = (const PanConfigNode*) child->data;

            if (pstring_equal (key, &data->key))
                  break;

            child = g_node_next_sibling (child);
      }

      return child;
}

static GNode *
config_search_node (const char * key)
{
      GNode    * node = config_root;
      const char * march;
      PString str = PSTRING_INIT;

      g_return_val_if_fail (config_root!=NULL, NULL);
      g_return_val_if_fail (is_nonempty_string(key), NULL);
      g_return_val_if_fail (*key=='/', NULL);

      march = key + 1;
      while (node!=NULL && get_next_token_pstring (march, '/', &march, &str))
            node = config_scan_children (node, &str);

      return node;
}


static void
config_set_or_add_node (const char        * key, 
                        PanConfigNodeType   type,
                        ...)
{
      GNode * node = config_root;
      va_list ap;
      const char * march = NULL;
      PString str = PSTRING_INIT;

      /* sanity clause */
      g_return_if_fail (config_root!=NULL);
      g_return_if_fail (is_nonempty_string(key));
      g_return_if_fail (*key=='/');

      /* loop down the key */
      march = key + 1;
      va_start (ap, type);
      while (get_next_token_pstring (march, '/', &march, &str))
      {
            GNode * child = config_scan_children (node, &str);
            const gboolean is_bottom = str.str[str.len] == '\0';

            if (child == NULL)
            {
                  PanConfigNode * data = is_bottom
                        ? config_create_node (&str, type, ap)
                        : config_create_empty_node (&str);

                  child = g_node_append (node, g_node_new(data));
            }
            else if (is_bottom)
            {
                  config_node_deleter (child, NULL);
                  child->data = config_create_node (&str, type, ap);
            }

            node = child;
      }
      va_end (ap);

      config_is_dirty = TRUE;
}

/**
***
**/

static void
get_config_filename (char * buf, int buflen)
{
      g_snprintf (buf, buflen,
                  "%s%cconfig.xml", get_data_dir(), G_DIR_SEPARATOR);
}

/**
***  Saving PanConfig
**/

static xmlNodePtr
write_node_recursive (xmlDocPtr doc, GNode * gnode)
{
      static const char * types [PAN_CONFIG_NODE_TYPE_QTY] = { "?", "s", "b", "n", "u" };
      PanConfigNode * config;
      xmlNodePtr xnode;

      /* sanity clause */
      g_return_val_if_fail (doc!=NULL, NULL);
      g_return_val_if_fail (gnode!=NULL, NULL);
      g_return_val_if_fail (gnode->data!=NULL, NULL);

      /* current */
      config = (PanConfigNode*) gnode->data;
      if (config->type == IS_NOTYPE) /* it's a section */
      {
            xnode = xmlNewDocNode (doc, NULL, "section", NULL);
            xmlSetProp (xnode, "key", config->key.str);
      }
      else /* it's a key */
      {
            char buf[64];
            char * content = buf;
            *buf = '\0';

            switch (config->type) {
                  case IS_STRING:         content = config->value.val_string ? config->value.val_string : ""; break;
                  case IS_BOOL:           content = config->value.val_int ? "true" : "false"; break;
                  case IS_INT:            g_snprintf (buf, sizeof(buf), "%d", config->value.val_int); break;
                    case IS_UNSIGNED_LONG:  g_snprintf (buf, sizeof(buf), "%lu", config->value.val_ulong); break;
                        default:                pan_warn_if_reached (); break;
            }

            xnode = xmlNewDocNode (doc, NULL, "value", NULL);
            xmlSetProp (xnode, "key", config->key.str);
            xmlSetProp (xnode, "type", types[config->type]);
            xmlNodeAddContent (xnode, content);
      }

      /* children */
      if (gnode->children != NULL) {
            GNode * tmp;
            for (tmp=gnode->children; tmp!=NULL; tmp=tmp->next)
                  xmlAddChild (xnode, write_node_recursive (doc, tmp));
      }

      return xnode;
}

static void
pan_config_save (void)
{
      if (config_root != NULL)
      {
            GNode * tmp;
            char filename[PATH_MAX];
            xmlDocPtr doc;

            /* build the tree */
            doc = xmlNewDoc ("1.0");
            doc->children = xmlNewDocNode (doc, NULL, "config", NULL);
            for (tmp=config_root->children; tmp!=NULL; tmp=tmp->next)
                  xmlAddChild (doc->children, write_node_recursive (doc, tmp));

            /* write it out */
            get_config_filename (filename, sizeof(filename));
            xmlSaveFormatFileEnc (filename, doc, "UTF-8", 1);
            xmlFreeDoc (doc);
      }
}

void
pan_config_sync (void)
{
      if (config_is_dirty)
      {
            pan_config_save ();
            config_is_dirty = FALSE;
      }
}


/**
 ** Loading PanConfig
 **/

static void
parse_and_load_file (xmlDoc *doc, xmlNode *node, const char * path)
{

      g_return_if_fail (doc!=NULL);
      g_return_if_fail (node!=NULL);

      for (; node!=NULL; node=node->next)
      {
            if (!pan_strcmp (node->name, "section")) {

                  /* section: recurse through the xml structure */

                  xmlChar * key = NULL;
                  char    * new_path = NULL;

                  key = xmlGetProp (node, "key");
                  new_path = g_strdup_printf ("%s/%s", path, key);
                  xmlFree (key);

                  parse_and_load_file (doc, node->xmlChildrenNode, 
                        new_path);

                  g_free (new_path);
            }
            else
            if (!pan_strcmp (node->name, "value")) {

                  /* add a value */

                  xmlChar  * key = NULL;
                  xmlChar  * type = NULL;
                  xmlChar  * raw = NULL;
                  char     * contents = NULL;
                  char     * new_key = NULL;

                  key  = xmlGetProp (node, "key");
                  type = xmlGetProp (node, "type");
                  new_key = g_strdup_printf ("%s/%s", path, key);

                  if (node->xmlChildrenNode) {
                        raw = xmlNodeListGetString (doc, node->xmlChildrenNode, FALSE);
                  }

                  contents = raw ? pan_str_unescape ((const char *) raw) : g_strdup ("");

                  switch (type[0]) {
                  case 's':
                        config_set_or_add_node (new_key, IS_STRING, contents);

                        break;
                  case 'n':
                        config_set_or_add_node (new_key, IS_INT, atoi (contents));
                        break;
                  case 'b':
                        if (isdigit((guchar)contents[0]))
                              config_set_or_add_node (new_key, IS_BOOL, contents[0] == '1');
                        else
                              config_set_or_add_node (new_key, IS_BOOL, tolower(contents[0]) == 't');
                        break;
                  case 'u':
                        config_set_or_add_node (new_key, IS_UNSIGNED_LONG, strtoul(contents, NULL, 10));
                  }

                  g_free (new_key);
                  g_free (contents);
                  xmlFree (raw);
                  xmlFree (key);
                  xmlFree (type);
            }
      }
}

static void
pan_config_load_ini (void)
{
      int qty = 0;
      const char * march;
      char * pch;
      char * file;
      char * filename;
      char * category = NULL;
      GString * line;

      /* read the ini file */
      log_add (LOG_INFO, "can't find config.xml file; trying to import for ~/.gnome/Pan");
      filename = g_build_filename (pan_get_home_dir(), ".gnome", "Pan", NULL);
      g_file_get_contents (filename, &file, NULL, NULL);

      /* march through the file */
      march = file;
      line = g_string_new (NULL);
      while (get_next_token_g_str (march, '\n', &march, line))
      {
            /* clean the line */
            pan_g_string_strstrip (line);
            if (!line->len)
                  continue;

            /* check for new category */
            if (*line->str == '[') {
                  replace_gstr (&category, g_strdup (line->str+1));
                  if ((pch = strchr (category, ']')))
                        *pch = '\0';
            }

            /* check for a value */
            pch = strchr (line->str, '=');
            if (pch != NULL)
            {
                  char * freeme;
                  const char * val_utf8;
                  char * key = g_strndup (line->str, pch-line->str);
                  char * val = g_strdup (pch+1);

                  g_strstrip (key);
                  g_strstrip (val);
                  val_utf8 = pan_utf8ize (val, -1, &freeme);

                  if (strstr(key,"font") == NULL) /* omit pre-Pango font settings */
                  {
                        char * path = g_strdup_printf ("/Pan/%s/%s", category, key);
                        config_set_or_add_node (path, IS_STRING, val_utf8);
                        g_free (path);

                        ++qty;
                  }

                  g_free (freeme);
                  g_free (val);
                  g_free (key);
            }
      }

      if (qty)
            log_add_va (LOG_INFO, _("Imported %d lines from Gnome config file"), qty);

      g_string_free (line, TRUE);
      g_free (file);
      g_free (filename);
      g_free (category);
}

static void
pan_config_load (void)
{
      const PString  root_string = pstring_shallow ("", 0);
      xmlDocPtr      doc;
      xmlNodePtr     node;
      char           fname[PATH_MAX];

      g_return_if_fail (config_root==NULL);

      if (config_is_dirty) {
            g_warning ("Trying to load over an unsynced pan_config. Aborting");
            return ;
      }

      config_root = g_node_new (config_create_empty_node (&root_string));

      get_config_filename (fname, sizeof(fname));

      if (!pan_file_exists (fname)) {
            pan_config_load_ini ();
            return;
      }

      doc = xmlParseFile (fname);
      g_return_if_fail (doc!=NULL);

      node = xmlDocGetRootElement (doc);
      g_return_if_fail (node!=NULL);

      if (pan_strcmp ((const char *) node->name, "config"))
            g_warning (_("%s does not appear to be a valid datafile"), fname);
      else 
            parse_and_load_file (doc, node->xmlChildrenNode, "");

      xmlFreeDoc (doc);
}

/**
 ** Prefix Management
 **/

static char * prefix = NULL;

static void
compose_key (char * buf, int buf_max, const char * key)
{
      if (prefix)
            g_snprintf (buf, buf_max, "%s/%s", prefix, key);
      else
            g_strlcpy (buf, key, buf_max);
}

void
pan_config_push_prefix (const char * new_prefix)
{
      int prefix_len;

      g_return_if_fail (new_prefix != NULL);

      replace_gstr (&prefix, NULL);

      prefix_len = strlen (new_prefix);
      prefix = g_memdup (new_prefix, prefix_len+1);
      if (prefix[prefix_len-1] == '/')
            prefix[prefix_len-1] = '\0';
}

void
pan_config_pop_prefix (void)
{
      replace_gstr (&prefix, NULL);
}


/**
 ** GETTING 
 **/

static void
ensure_config_loaded (void)
{
      if (config_root == NULL)
            pan_config_load ();
}

char*
pan_config_get_string (const char * key, const char * default_value)
{
      char              full_key[MAX_KEY_LEN];
      char            * val;
      GNode           * node;
      PanConfigNode   * data;
      debug_enter ("pan_config_get_string");

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      node = config_search_node (full_key);
      data = (PanConfigNode *) (node ? node->data : NULL);

      val = NULL;

      if (data!=NULL && data->type == IS_STRING)
            val = g_strdup (data->value.val_string);

      if (!is_nonempty_string (val))
            replace_gstr (&val, g_strdup (default_value));

      debug_exit ("pan_config_get_string");
      return val;
}

gboolean
pan_config_get_bool (const char * key, gboolean default_value)
{
      char            full_key[MAX_KEY_LEN];
      gboolean        val;
      GNode         * node;
      PanConfigNode * data;

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      node = config_search_node (full_key);
      data = (PanConfigNode *) (node ? node->data : NULL);

      if (data == NULL)                  val = default_value;
      else if (data->type == IS_BOOL)    val = data->value.val_bool;
      else if (data->type == IS_INT)     val = data->value.val_int != 0;
      else if (data->type == IS_STRING)  val = data->value.val_string!=NULL && tolower(data->value.val_string[0])=='t';
      else                               val = default_value;

      return val;
}


int
pan_config_get_int (const char * key, int default_value)
{
      int                val;
      char               full_key[MAX_KEY_LEN];
      GNode            * node;
      PanConfigNode    * data;
      debug_enter ("pan_config_get_int");

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      node = config_search_node (full_key);
      data = (PanConfigNode *) (node ? node->data : NULL);

      if (data == NULL)                   val = default_value;
      else if (data->type == IS_INT)          val = data->value.val_int;
      else if (data->type == IS_STRING)   val = atoi (data->value.val_string);
      else                                val = default_value;

      debug_exit ("pan_config_get_int");
      return val;
}

gulong
pan_config_get_ulong (const char * key, gulong default_value)
{
      gulong             val;
      char               full_key[MAX_KEY_LEN];
      GNode            * node;
      PanConfigNode    * data;
      debug_enter ("pan_config_get_ulong");

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      node = config_search_node (full_key);
      data = (PanConfigNode *) (node ? node->data : NULL);

      if (data == NULL)                          val = default_value;
      else if (data->type == IS_UNSIGNED_LONG)   val = data->value.val_ulong;
      else if (data->type == IS_STRING)          val = strtoul (data->value.val_string, NULL, 10);
      else                                       val = default_value;

      debug_exit ("pan_config_get_ulong");
      return val;
}


gboolean
pan_config_has_section (const char * section)
{
      GNode         * node = NULL;
      PanConfigNode * data = NULL;

      ensure_config_loaded ();

      node = config_search_node (section);
      data = (PanConfigNode *) (node ? node->data : NULL);

      return data && data->type == IS_NOTYPE;
}


/**
 ** SETTING
 **/

void
pan_config_set_string (const char * key, const char * value)
{
      char full_key[MAX_KEY_LEN];

      ensure_config_loaded ();

      if (value == NULL)
            value = "";

      compose_key (full_key, sizeof(full_key), key);
      config_set_or_add_node (full_key, IS_STRING, value);
}

void
pan_config_set_string_if_different (const char * key, const char * value, const char * default_value)
{
      if (!pan_strcmp (value, default_value))
            pan_config_clean_key (key);
      else
            pan_config_set_string (key, value);
}

void
pan_config_set_bool (const char * key, gboolean value)
{
      char full_key[MAX_KEY_LEN];

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      config_set_or_add_node (full_key, IS_BOOL, value);
}

void
pan_config_set_bool_if_different (const char * key, gboolean value, gboolean default_value)
{
      value = value != 0;
      default_value = default_value != 0;

      if (value == default_value)
            pan_config_clean_key (key);
      else
            pan_config_set_bool (key, value);
}


void
pan_config_set_int (const char * key, int value)
{
      char full_key[MAX_KEY_LEN];

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      config_set_or_add_node (full_key, IS_INT, value);
}

void
pan_config_set_int_if_different (const char * key, int value, int default_value)
{
      if (value == default_value)
            pan_config_clean_key (key);
      else
            pan_config_set_int (key, value);
}

void
pan_config_set_ulong (const char * key, gulong value)
{
      char full_key[MAX_KEY_LEN];

      ensure_config_loaded ();

      compose_key (full_key, sizeof(full_key), key);
      config_set_or_add_node (full_key, IS_UNSIGNED_LONG, value);
}

void
pan_config_set_ulong_if_different (const char * key, gulong value, gulong default_value)
{
      if (value == default_value)
            pan_config_clean_key (key);
      else
            pan_config_set_ulong (key, value);
}

void
pan_config_clean_key (const char * key)
{
      GNode * node;

      ensure_config_loaded ();

      node = config_search_node (key);

      if (node)
      {
            g_return_if_fail (g_node_n_children (node) == 0);

            config_delete_node (node);
      }
}


void
pan_config_clean_section (const char * section)
{
      GNode * node;

      ensure_config_loaded ();

      node = config_search_node (section);

      if (node)
      {
            config_delete_node (node);
      }
}

Generated by  Doxygen 1.6.0   Back to index