gimpconfig-iface.c 19.9 KB
Newer Older
1 2 3
/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * Config file serialization and deserialization interface
5
 * Copyright (C) 2001-2002  Sven Neumann <sven@gimp.org>
6
 *
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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"

24
#include <string.h>
25 26 27

#include <glib-object.h>

28
#include "libgimpbase/gimpbase.h"
29

30 31
#include "config-types.h"

32
#include "gimpconfig.h"
33
#include "gimpconfig-deserialize.h"
34 35
#include "gimpconfig-serialize.h"
#include "gimpconfig-params.h"
36
#include "gimpconfig-utils.h"
37
#include "gimpconfigwriter.h"
38
#include "gimpscanner.h"
39

40
#include "gimp-intl.h"
41

42

43
/*
Sven Neumann's avatar
Sven Neumann committed
44 45
 * The GimpConfig serialization and deserialization interface.
 */
46

47
static void         gimp_config_iface_base_init (GimpConfigInterface  *config_iface);
48

49 50 51 52 53 54 55 56 57 58 59
static gboolean     gimp_config_iface_serialize   (GimpConfig       *config,
                                                   GimpConfigWriter *writer,
                                                   gpointer          data);
static gboolean     gimp_config_iface_deserialize (GimpConfig       *config,
                                                   GScanner         *scanner,
                                                   gint              nest_level,
                                                   gpointer          data);
static GimpConfig * gimp_config_iface_duplicate   (GimpConfig       *config);
static gboolean     gimp_config_iface_equal       (GimpConfig       *a,
                                                   GimpConfig       *b);
static void         gimp_config_iface_reset       (GimpConfig       *config);
60 61 62


GType
63
gimp_config_interface_get_type (void)
64
{
65
  static GType config_iface_type = 0;
66

67
  if (!config_iface_type)
68
    {
69
      static const GTypeInfo config_iface_info =
70
      {
71
        sizeof (GimpConfigInterface),
72
	(GBaseInitFunc)     gimp_config_iface_base_init,
73 74 75
	(GBaseFinalizeFunc) NULL,
      };

76 77 78
      config_iface_type = g_type_register_static (G_TYPE_INTERFACE,
                                                  "GimpConfigInterface",
                                                  &config_iface_info,
79
                                                  0);
80

81
      g_type_interface_add_prerequisite (config_iface_type, G_TYPE_OBJECT);
82
    }
83

84
  return config_iface_type;
85 86 87
}

static void
88
gimp_config_iface_base_init (GimpConfigInterface *config_iface)
89
{
90 91 92 93 94 95 96 97 98 99 100 101 102 103
  if (! config_iface->serialize)
    {
      config_iface->serialize   = gimp_config_iface_serialize;
      config_iface->deserialize = gimp_config_iface_deserialize;
      config_iface->duplicate   = gimp_config_iface_duplicate;
      config_iface->equal       = gimp_config_iface_equal;
      config_iface->reset       = gimp_config_iface_reset;
    }

  /*  always set these to NULL since we don't want to inherit them
   *  from parent classes
   */
  config_iface->serialize_property   = NULL;
  config_iface->deserialize_property = NULL;
104 105
}

106
static gboolean
107
gimp_config_iface_serialize (GimpConfig       *config,
108 109
			     GimpConfigWriter *writer,
                             gpointer          data)
110
{
111
  return gimp_config_serialize_properties (config, writer);
112
}
113

114
static gboolean
115 116 117 118
gimp_config_iface_deserialize (GimpConfig *config,
                               GScanner   *scanner,
                               gint        nest_level,
                               gpointer    data)
119
{
120
  return gimp_config_deserialize_properties (config,
121
                                             scanner, nest_level, FALSE);
122 123
}

124 125
static GimpConfig *
gimp_config_iface_duplicate (GimpConfig *config)
126
{
127 128 129 130 131 132
  GObjectClass  *klass;
  GParamSpec   **property_specs;
  guint          n_property_specs;
  GParameter    *construct_params   = NULL;
  gint           n_construct_params = 0;
  guint          i;
133
  GimpConfig    *dup;
134

135
  klass = G_OBJECT_GET_CLASS (config);
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

  property_specs = g_object_class_list_properties (klass, &n_property_specs);

  construct_params = g_new0 (GParameter, n_property_specs);

  for (i = 0; i < n_property_specs; i++)
    {
      GParamSpec *prop_spec = property_specs[i];

      if ((prop_spec->flags & G_PARAM_READABLE) &&
          (prop_spec->flags & G_PARAM_WRITABLE) &&
          (prop_spec->flags & G_PARAM_CONSTRUCT_ONLY))
        {
          GParameter *construct_param;

          construct_param = &construct_params[n_construct_params++];

          construct_params->name = prop_spec->name;

          g_value_init (&construct_params->value, prop_spec->value_type);
156
          g_object_get_property (G_OBJECT (config), prop_spec->name,
157 158 159 160 161 162
                                 &construct_param->value);
        }
    }

  g_free (property_specs);

163
  dup = g_object_newv (G_TYPE_FROM_INSTANCE (config),
164 165 166 167 168 169
                       n_construct_params, construct_params);

  for (i = 0; i < n_construct_params; i++)
    g_value_unset (&construct_params[i].value);

  g_free (construct_params);
170

171
  gimp_config_sync (config, dup, 0);
172 173 174 175 176

  return dup;
}

static gboolean
177 178
gimp_config_iface_equal (GimpConfig *a,
                         GimpConfig *b)
179 180 181 182 183 184 185 186 187 188
{
  GObjectClass  *klass;
  GParamSpec   **property_specs;
  guint          n_property_specs;
  guint          i;
  gboolean       equal = TRUE;

  klass = G_OBJECT_GET_CLASS (a);

  property_specs = g_object_class_list_properties (klass, &n_property_specs);
189

190 191 192 193 194 195 196 197 198 199 200 201 202
  for (i = 0; equal && i < n_property_specs; i++)
    {
      GParamSpec  *prop_spec;
      GValue       a_value = { 0, };
      GValue       b_value = { 0, };

      prop_spec = property_specs[i];

      if (! (prop_spec->flags & G_PARAM_READABLE))
        continue;

      g_value_init (&a_value, prop_spec->value_type);
      g_value_init (&b_value, prop_spec->value_type);
203 204
      g_object_get_property (G_OBJECT (a), prop_spec->name, &a_value);
      g_object_get_property (G_OBJECT (b), prop_spec->name, &b_value);
205

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
      if (g_param_values_cmp (prop_spec, &a_value, &b_value))
        {
          if ((prop_spec->flags & GIMP_PARAM_AGGREGATE) &&
              G_IS_PARAM_SPEC_OBJECT (prop_spec)        &&
              g_type_interface_peek (g_type_class_peek (prop_spec->value_type),
                                     GIMP_TYPE_CONFIG))
            {
              if (! gimp_config_is_equal_to (g_value_get_object (&a_value),
                                             g_value_get_object (&b_value)))
                {
                  equal = FALSE;
                }
            }
          else
            {
              equal = FALSE;
            }
        }
224 225 226 227 228 229 230 231 232 233

      g_value_unset (&a_value);
      g_value_unset (&b_value);
    }

  g_free (property_specs);

  return equal;
}

234
static void
235
gimp_config_iface_reset (GimpConfig *config)
236
{
237
  gimp_config_reset_properties (config);
238 239
}

240
/**
241
 * gimp_config_serialize_to_file:
242
 * @config: a #GObject that implements the #GimpConfigInterface.
243
 * @filename: the name of the file to write the configuration to.
244 245
 * @header: optional file header (must be ASCII only)
 * @footer: optional file footer (must be ASCII only)
246
 * @data: user data passed to the serialize implementation.
247
 * @error:
248
 *
249
 * Serializes the object properties of @config to the file specified
250
 * by @filename. If a file with that name already exists, it is
251
 * overwritten. Basically this function opens @filename for you and
252
 * calls the serialize function of the @config's #GimpConfigInterface.
253 254 255
 *
 * Return value: %TRUE if serialization succeeded, %FALSE otherwise.
 **/
256
gboolean
257
gimp_config_serialize_to_file (GimpConfig   *config,
258 259 260 261 262
			       const gchar  *filename,
			       const gchar  *header,
			       const gchar  *footer,
			       gpointer      data,
			       GError      **error)
263
{
264
  GimpConfigWriter *writer;
265

266
  g_return_val_if_fail (GIMP_IS_CONFIG (config), FALSE);
267
  g_return_val_if_fail (filename != NULL, FALSE);
268
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
269

270
  writer = gimp_config_writer_new_file (filename, TRUE, header, error);
271 272
  if (!writer)
    return FALSE;
Sven Neumann's avatar
Sven Neumann committed
273

274
  GIMP_CONFIG_GET_INTERFACE (config)->serialize (config, writer, data);
275

276
  return gimp_config_writer_finish (writer, footer, error);
277 278
}

279
gboolean
280 281 282
gimp_config_serialize_to_fd (GimpConfig *config,
                             gint        fd,
                             gpointer    data)
283
{
284
  GimpConfigWriter *writer;
285

286
  g_return_val_if_fail (GIMP_IS_CONFIG (config), FALSE);
287 288 289 290 291 292
  g_return_val_if_fail (fd > 0, FALSE);

  writer = gimp_config_writer_new_fd (fd);
  if (!writer)
    return FALSE;

293
  GIMP_CONFIG_GET_INTERFACE (config)->serialize (config, writer, data);
294 295 296 297

  return gimp_config_writer_finish (writer, NULL, NULL);
}

298 299
/**
 * gimp_config_serialize_to_string:
300
 * @config: a #GObject that implements the #GimpConfigInterface.
301
 * @data: user data passed to the serialize implementation.
302
 *
303
 * Serializes the object properties of @config to a string.
304 305 306 307
 *
 * Return value: a newly allocated %NUL-terminated string.
 **/
gchar *
308 309
gimp_config_serialize_to_string (GimpConfig *config,
				 gpointer    data)
310
{
311 312
  GimpConfigWriter *writer;
  GString          *str;
313

314
  g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL);
315 316 317 318

  str = g_string_new (NULL);
  writer = gimp_config_writer_new_string (str);

319
  GIMP_CONFIG_GET_INTERFACE (config)->serialize (config, writer, data);
320 321 322 323 324 325

  gimp_config_writer_finish (writer, NULL, NULL);

  return g_string_free (str, FALSE);
}

326 327
/**
 * gimp_config_deserialize:
328
 * @config: a #GObject that implements the #GimpConfigInterface.
329
 * @filename: the name of the file to read configuration from.
Sven Neumann's avatar
Sven Neumann committed
330
 * @data: user data passed to the deserialize implementation.
331 332
 * @error:
 *
333
 * Opens the file specified by @filename, reads configuration data
334
 * from it and configures @config accordingly. Basically this function
335
 * creates a properly configured #GScanner for you and calls the
336
 * deserialize function of the @config's #GimpConfigInterface.
337 338
 *
 * Return value: %TRUE if deserialization succeeded, %FALSE otherwise.
339
 **/
340
gboolean
341
gimp_config_deserialize_file (GimpConfig   *config,
342 343 344
			      const gchar  *filename,
			      gpointer      data,
			      GError      **error)
345
{
346 347
  GScanner *scanner;
  gboolean  success;
348

349
  g_return_val_if_fail (GIMP_IS_CONFIG (config), FALSE);
350
  g_return_val_if_fail (filename != NULL, FALSE);
351
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
352

353
  scanner = gimp_scanner_new_file (filename, error);
354 355
  if (! scanner)
    return FALSE;
356

357 358
  success = GIMP_CONFIG_GET_INTERFACE (config)->deserialize (config,
                                                             scanner, 0, data);
359

360
  gimp_scanner_destroy (scanner);
361

362 363 364
  if (! success)
    g_assert (error == NULL || *error != NULL);

365 366
  return success;
}
367

368 369
/**
 * gimp_config_deserialize_string:
370
 * @config: a #GObject that implements the #GimpConfigInterface.
371 372
 * @text: string to deserialize (in UTF-8 encoding)
 * @text_len: length of @text in bytes or -1
373
 * @data:
374 375
 * @error:
 *
376
 * Configures @config from @text. Basically this function creates a
377
 * properly configured #GScanner for you and calls the deserialize
378
 * function of the @config's #GimpConfigInterface.
379
 *
380
 * Returns: %TRUE if deserialization succeeded, %FALSE otherwise.
381 382
 **/
gboolean
383
gimp_config_deserialize_string (GimpConfig      *config,
384 385 386 387 388
                                const gchar  *text,
                                gint          text_len,
				gpointer      data,
                                GError      **error)
{
389 390
  GScanner *scanner;
  gboolean  success;
391

392
  g_return_val_if_fail (GIMP_IS_CONFIG (config), FALSE);
393 394 395 396 397
  g_return_val_if_fail (text != NULL || text_len == 0, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  scanner = gimp_scanner_new_string (text, text_len, error);

398 399
  success = GIMP_CONFIG_GET_INTERFACE (config)->deserialize (config,
                                                             scanner, 0, data);
400 401 402 403 404 405 406 407 408

  gimp_scanner_destroy (scanner);

  if (! success)
    g_assert (error == NULL || *error != NULL);

  return success;
}

409 410 411
gboolean
gimp_config_deserialize_return (GScanner     *scanner,
                                GTokenType    expected_token,
412
                                gint          nest_level)
413 414 415 416 417 418 419 420 421 422
{
  GTokenType next_token;

  g_return_val_if_fail (scanner != NULL, FALSE);

  next_token = g_scanner_peek_next_token (scanner);

  if (expected_token != G_TOKEN_LEFT_PAREN)
    {
      g_scanner_get_next_token (scanner);
423
      g_scanner_unexp_token (scanner, expected_token, NULL, NULL, NULL,
424 425 426 427 428 429 430 431 432 433 434 435
                             _("fatal parse error"), TRUE);
      return FALSE;
    }
  else
    {
      if (nest_level > 0 && next_token == G_TOKEN_RIGHT_PAREN)
        {
          return TRUE;
        }
      else if (next_token != G_TOKEN_EOF)
        {
          g_scanner_get_next_token (scanner);
436
          g_scanner_unexp_token (scanner, expected_token, NULL, NULL, NULL,
437 438 439 440 441 442 443 444
                                 _("fatal parse error"), TRUE);
          return FALSE;
        }
    }

  return TRUE;
}

445

446 447
/**
 * gimp_config_duplicate:
448
 * @config: a #GObject that implements the #GimpConfigInterface.
449
 *
450 451 452 453
 * Creates a copy of the passed object by copying all object
 * properties. The default implementation of the #GimpConfigInterface
 * only works for objects that are completely defined by their
 * properties.
454
 *
455
 * Return value: the duplicated #GimpConfig object
456
 **/
457
gpointer
458
gimp_config_duplicate (GimpConfig *config)
459
{
460
  g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL);
461

462
  return GIMP_CONFIG_GET_INTERFACE (config)->duplicate (config);
463 464 465 466 467 468
}

/**
 * gimp_config_is_equal_to:
 * @a: a #GObject that implements the #GimpConfigInterface.
 * @b: another #GObject of the same type as @a.
469
 *
470 471 472 473
 * Compares the two objects. The default implementation of the
 * #GimpConfigInterface compares the object properties and thus only
 * works for objects that are completely defined by their
 * properties.
474
 *
475 476 477
 * Return value: %TRUE if the two objects are equal.
 **/
gboolean
478 479
gimp_config_is_equal_to (GimpConfig *a,
                         GimpConfig *b)
480
{
481 482
  g_return_val_if_fail (GIMP_IS_CONFIG (a), FALSE);
  g_return_val_if_fail (GIMP_IS_CONFIG (b), FALSE);
483
  g_return_val_if_fail (G_TYPE_FROM_INSTANCE (a) == G_TYPE_FROM_INSTANCE (b),
484 485
                        FALSE);

486
  return GIMP_CONFIG_GET_INTERFACE (a)->equal (a, b);
487 488
}

489 490
/**
 * gimp_config_reset:
491
 * @config: a #GObject that implements the #GimpConfigInterface.
492
 *
493 494 495 496 497
 * Resets the object to its default state. The default implementation of the
 * #GimpConfigInterface only works for objects that are completely defined by
 * their properties.
 **/
void
498
gimp_config_reset (GimpConfig *config)
499
{
500
  g_return_if_fail (GIMP_IS_CONFIG (config));
501

502
  GIMP_CONFIG_GET_INTERFACE (config)->reset (config);
503 504
}

505

506
/*
Sven Neumann's avatar
Sven Neumann committed
507 508 509 510 511 512 513 514 515 516 517 518 519 520
 * Code to store and lookup unknown tokens (string key/value pairs).
 */

#define GIMP_CONFIG_UNKNOWN_TOKENS "gimp-config-unknown-tokens"

typedef struct
{
  gchar *key;
  gchar *value;
} GimpConfigToken;

static void  gimp_config_destroy_unknown_tokens (GSList   *unknown_tokens);


521 522
/**
 * gimp_config_add_unknown_token:
523
 * @config: a #GObject.
524 525
 * @key: a nul-terminated string to identify the value.
 * @value: a nul-terminated string representing the value.
526
 *
527 528
 * This function allows to add arbitrary key/value pairs to a GObject.
 * It's purpose is to attach additional data to a #GimpConfig object
529 530 531 532
 * that can be stored along with the object properties when
 * serializing the object to a configuration file. Please note however
 * that the default gimp_config_serialize() implementation does not
 * serialize unknown tokens.
533 534
 *
 * If you want to remove a key/value pair from the object, call this
535
 * function with a %NULL @value.
536
 **/
537
void
538
gimp_config_add_unknown_token (GimpConfig  *config,
Sven Neumann's avatar
Sven Neumann committed
539 540
                               const gchar *key,
                               const gchar *value)
541
{
Sven Neumann's avatar
Sven Neumann committed
542 543 544 545
  GimpConfigToken *token;
  GSList          *unknown_tokens;
  GSList          *last;
  GSList          *list;
546

547
  g_return_if_fail (GIMP_IS_CONFIG (config));
548 549
  g_return_if_fail (key != NULL);

550
  unknown_tokens = (GSList *) g_object_get_data (G_OBJECT (config),
Sven Neumann's avatar
Sven Neumann committed
551 552
                                                 GIMP_CONFIG_UNKNOWN_TOKENS);

553 554
  for (last = NULL, list = unknown_tokens;
       list;
Sven Neumann's avatar
Sven Neumann committed
555 556 557 558 559 560 561
       last = list, list = g_slist_next (list))
    {
      token = (GimpConfigToken *) list->data;

      if (strcmp (token->key, key) == 0)
        {
          g_free (token->value);
562

563 564
          if (value)
            {
565
              token->value = g_strdup (value);
566 567 568 569 570 571
            }
          else
            {
              g_free (token->key);

              unknown_tokens = g_slist_remove (unknown_tokens, token);
572 573
              g_object_set_data_full (G_OBJECT (config),
                                      GIMP_CONFIG_UNKNOWN_TOKENS,
574
                                      unknown_tokens,
575 576
                     (GDestroyNotify) gimp_config_destroy_unknown_tokens);
            }
577

Sven Neumann's avatar
Sven Neumann committed
578 579 580
          return;
        }
    }
581

582 583 584
  if (!value)
    return;

Sven Neumann's avatar
Sven Neumann committed
585 586
  token = g_new (GimpConfigToken, 1);
  token->key   = g_strdup (key);
587
  token->value = g_strdup (value);
Sven Neumann's avatar
Sven Neumann committed
588 589 590 591 592 593

  if (last)
    {
      g_slist_append (last, token);
    }
  else
594
    {
Sven Neumann's avatar
Sven Neumann committed
595
      unknown_tokens = g_slist_append (NULL, token);
596

597 598
      g_object_set_data_full (G_OBJECT (config),
                              GIMP_CONFIG_UNKNOWN_TOKENS,
599
                              unknown_tokens,
Sven Neumann's avatar
Sven Neumann committed
600
             (GDestroyNotify) gimp_config_destroy_unknown_tokens);
601 602 603
    }
}

604 605
/**
 * gimp_config_lookup_unknown_token:
606
 * @config: a #GObject.
607
 * @key: a nul-terminated string to identify the value.
608
 *
609 610 611
 * This function retrieves data that was previously attached using
 * gimp_config_add_unknown_token(). You should not free or modify
 * the returned string.
612 613
 *
 * Returns: a pointer to a constant string.
614
 **/
615
const gchar *
616
gimp_config_lookup_unknown_token (GimpConfig  *config,
617 618
                                  const gchar *key)
{
Sven Neumann's avatar
Sven Neumann committed
619 620 621
  GimpConfigToken *token;
  GSList          *unknown_tokens;
  GSList          *list;
622

623
  g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL);
624
  g_return_val_if_fail (key != NULL, NULL);
625

626
  unknown_tokens = (GSList *) g_object_get_data (G_OBJECT (config),
Sven Neumann's avatar
Sven Neumann committed
627
                                                 GIMP_CONFIG_UNKNOWN_TOKENS);
628

Sven Neumann's avatar
Sven Neumann committed
629 630 631
  for (list = unknown_tokens; list; list = g_slist_next (list))
    {
      token = (GimpConfigToken *) list->data;
632

Sven Neumann's avatar
Sven Neumann committed
633 634 635
      if (strcmp (token->key, key) == 0)
        return token->value;
    }
636

Sven Neumann's avatar
Sven Neumann committed
637 638
  return NULL;
}
639

640 641
/**
 * gimp_config_foreach_unknown_token:
642
 * @config: a #GObject.
643 644
 * @func: a function to call for each key/value pair.
 * @user_data: data to pass to @func.
645
 *
646
 * Calls @func for each key/value stored with the @config using
647 648
 * gimp_config_add_unknown_token().
 **/
649
void
650
gimp_config_foreach_unknown_token (GimpConfig            *config,
Sven Neumann's avatar
Sven Neumann committed
651 652
                                   GimpConfigForeachFunc  func,
                                   gpointer               user_data)
653
{
Sven Neumann's avatar
Sven Neumann committed
654 655 656 657
  GimpConfigToken *token;
  GSList          *unknown_tokens;
  GSList          *list;

658
  g_return_if_fail (GIMP_IS_CONFIG (config));
Sven Neumann's avatar
Sven Neumann committed
659
  g_return_if_fail (func != NULL);
660

661
  unknown_tokens = (GSList *) g_object_get_data (G_OBJECT (config),
Sven Neumann's avatar
Sven Neumann committed
662
                                                 GIMP_CONFIG_UNKNOWN_TOKENS);
663

Sven Neumann's avatar
Sven Neumann committed
664
  for (list = unknown_tokens; list; list = g_slist_next (list))
665
    {
Sven Neumann's avatar
Sven Neumann committed
666
      token = (GimpConfigToken *) list->data;
667

Sven Neumann's avatar
Sven Neumann committed
668
      func (token->key, token->value, user_data);
669
    }
Sven Neumann's avatar
Sven Neumann committed
670
}
671

Sven Neumann's avatar
Sven Neumann committed
672 673 674 675 676
static void
gimp_config_destroy_unknown_tokens (GSList *unknown_tokens)
{
  GimpConfigToken *token;
  GSList          *list;
677

Sven Neumann's avatar
Sven Neumann committed
678
  for (list = unknown_tokens; list; list = g_slist_next (list))
679
    {
Sven Neumann's avatar
Sven Neumann committed
680
      token = (GimpConfigToken *) list->data;
681

Sven Neumann's avatar
Sven Neumann committed
682 683 684
      g_free (token->key);
      g_free (token->value);
      g_free (token);
685
    }
686

Sven Neumann's avatar
Sven Neumann committed
687
  g_slist_free (unknown_tokens);
688
}