Make use of G_DEFINE_QUARK()
[platform/upstream/evolution-data-server.git] / camel / camel-object.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- *
2  *
3  * Author:
4  *  Michael Zucchi <notzed@ximian.com>
5  *
6  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20  * USA
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <glib/gstdio.h>
32
33 #include "camel-file-utils.h"
34 #include "camel-object.h"
35
36 #define d(x)
37
38 #define CAMEL_OBJECT_GET_PRIVATE(obj) \
39         (G_TYPE_INSTANCE_GET_PRIVATE \
40         ((obj), CAMEL_TYPE_OBJECT, CamelObjectPrivate))
41
42 struct _CamelObjectPrivate {
43         gchar *state_filename;
44 };
45
46 enum {
47         PROP_0,
48         PROP_STATE_FILENAME
49 };
50
51 G_DEFINE_ABSTRACT_TYPE (CamelObject, camel_object, G_TYPE_OBJECT)
52
53 /* State file for CamelObject data.
54  * Any later versions should only append data.
55  *
56  * version:uint32
57  *
58  * Version 0 of the file:
59  *
60  * version:uint32 = 0
61  * count:uint32                         -- count of meta-data items
62  * ( name:string value:string ) *count          -- meta-data items
63  *
64  * Version 1 of the file adds:
65  * count:uint32                                 -- count of persistent properties
66  * ( tag:uing32 value:tagtype ) *count          -- persistent properties
67  */
68
69 #define CAMEL_OBJECT_STATE_FILE_MAGIC "CLMD"
70
71 /* XXX This is a holdover from Camel's old homegrown type system.
72  *     CamelArg was a kind of primitive version of GObject properties.
73  *     The argument ID and data type were encoded into a 32-bit integer.
74  *     Unfortunately the encoding was also used in the binary state file
75  *     format, so we still need the secret decoder ring. */
76 enum camel_arg_t {
77         CAMEL_ARG_END = 0,
78         CAMEL_ARG_IGNORE = 1,   /* override/ignore an arg in-place */
79
80         CAMEL_ARG_FIRST = 1024, /* 1024 args reserved for arg system */
81
82         CAMEL_ARG_TYPE = 0xf0000000, /* type field for tags */
83         CAMEL_ARG_TAG = 0x0fffffff, /* tag field for args */
84
85         CAMEL_ARG_OBJ = 0x00000000, /* object */
86         CAMEL_ARG_INT = 0x10000000, /* gint */
87         CAMEL_ARG_DBL = 0x20000000, /* gdouble */
88         CAMEL_ARG_STR = 0x30000000, /* c string */
89         CAMEL_ARG_PTR = 0x40000000, /* ptr */
90         CAMEL_ARG_BOO = 0x50000000  /* bool */
91 };
92
93 #define CAMEL_ARGV_MAX (20)
94
95 static void
96 object_set_property (GObject *object,
97                      guint property_id,
98                      const GValue *value,
99                      GParamSpec *pspec)
100 {
101         switch (property_id) {
102                 case PROP_STATE_FILENAME:
103                         camel_object_set_state_filename (
104                                 CAMEL_OBJECT (object),
105                                 g_value_get_string (value));
106                         return;
107         }
108
109         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
110 }
111
112 static void
113 object_get_property (GObject *object,
114                      guint property_id,
115                      GValue *value,
116                      GParamSpec *pspec)
117 {
118         switch (property_id) {
119                 case PROP_STATE_FILENAME:
120                         g_value_set_string (
121                                 value, camel_object_get_state_filename (
122                                 CAMEL_OBJECT (object)));
123                         return;
124         }
125
126         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
127 }
128
129 static void
130 object_finalize (GObject *object)
131 {
132         CamelObjectPrivate *priv;
133
134         priv = CAMEL_OBJECT_GET_PRIVATE (object);
135
136         g_free (priv->state_filename);
137
138         /* Chain up to parent's finalize() method. */
139         G_OBJECT_CLASS (camel_object_parent_class)->finalize (object);
140 }
141
142 static gint
143 object_state_read (CamelObject *object,
144                    FILE *fp)
145 {
146         GValue value = G_VALUE_INIT;
147         GObjectClass *class;
148         GParamSpec **properties;
149         guint32 count, version;
150         guint ii, jj, n_properties;
151
152         if (camel_file_util_decode_uint32 (fp, &version) == -1)
153                 return -1;
154
155         if (version > 1)
156                 return -1;
157
158         if (camel_file_util_decode_uint32 (fp, &count) == -1)
159                 return -1;
160
161         /* XXX Camel no longer supports meta-data in state
162          *     files, so we're just eating dead data here. */
163         for (ii = 0; ii < count; ii++) {
164                 gchar *name = NULL;
165                 gchar *value = NULL;
166                 gboolean success;
167
168                 success =
169                         camel_file_util_decode_string (fp, &name) == 0 &&
170                         camel_file_util_decode_string (fp, &value) == 0;
171
172                 g_free (name);
173                 g_free (value);
174
175                 if (!success)
176                         return -1;
177         }
178
179         if (version == 0)
180                 return 0;
181
182         if (camel_file_util_decode_uint32 (fp, &count) == -1)
183                 return 0;
184
185         if (count == 0 || count > 1024)
186                 /* Maybe it was just version 0 afterall. */
187                 return 0;
188
189         count = MIN (count, CAMEL_ARGV_MAX);
190
191         class = G_OBJECT_GET_CLASS (object);
192         properties = g_object_class_list_properties (class, &n_properties);
193
194         for (ii = 0; ii < count; ii++) {
195                 gboolean property_set = FALSE;
196                 guint32 tag, v_uint32;
197
198                 if (camel_file_util_decode_uint32 (fp, &tag) == -1)
199                         goto exit;
200
201                 /* Record state file values into GValues.
202                  * XXX We currently only support booleans. */
203                 switch (tag & CAMEL_ARG_TYPE) {
204                         case CAMEL_ARG_BOO:
205                                 if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
206                                         goto exit;
207                                 g_value_init (&value, G_TYPE_BOOLEAN);
208                                 g_value_set_boolean (&value, (gboolean) v_uint32);
209                                 break;
210                         default:
211                                 g_warn_if_reached ();
212                                 goto exit;
213                 }
214
215                 /* Now we have to match the legacy numeric CamelArg tag
216                  * value with a GObject property.  The GObject property
217                  * IDs have been set to the same legacy tag values, but
218                  * we have to access a private GParamSpec field to get
219                  * to them (pspec->param_id). */
220
221                 tag &= CAMEL_ARG_TAG;  /* filter out the type code */
222
223                 for (jj = 0; jj < n_properties; jj++) {
224                         GParamSpec *pspec = properties[jj];
225
226                         if (pspec->param_id != tag)
227                                 continue;
228
229                         /* Sanity check. */
230                         g_warn_if_fail (pspec->flags & CAMEL_PARAM_PERSISTENT);
231                         if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
232                                 continue;
233
234                         g_object_set_property (
235                                 G_OBJECT (object), pspec->name, &value);
236
237                         property_set = TRUE;
238                         break;
239                 }
240
241                 /* XXX This tag was used by the old IMAP backend.
242                  *     It may still show up in accounts that were
243                  *     migrated from IMAP to IMAPX.  Silence the
244                  *     warning. */
245                 if (tag == 0x2500)
246                         property_set = TRUE;
247
248                 if (!property_set)
249                         g_warning (
250                                 "Could not find a corresponding %s "
251                                 "property for state file tag 0x%x",
252                                 G_OBJECT_TYPE_NAME (object), tag);
253
254                 g_value_unset (&value);
255         }
256
257 exit:
258         g_free (properties);
259
260         return 0;
261 }
262
263 static gint
264 object_state_write (CamelObject *object,
265                     FILE *fp)
266 {
267         GValue value = G_VALUE_INIT;
268         GObjectClass *class;
269         GParamSpec **properties;
270         guint ii, n_properties;
271         guint32 n_persistent = 0;
272
273         class = G_OBJECT_GET_CLASS (object);
274         properties = g_object_class_list_properties (class, &n_properties);
275
276         /* Version = 1 */
277         if (camel_file_util_encode_uint32 (fp, 1) == -1)
278                 goto exit;
279
280         /* No meta-data items. */
281         if (camel_file_util_encode_uint32 (fp, 0) == -1)
282                 goto exit;
283
284         /* Count persistent properties. */
285         for (ii = 0; ii < n_properties; ii++)
286                 if (properties[ii]->flags & CAMEL_PARAM_PERSISTENT)
287                         n_persistent++;
288
289         if (camel_file_util_encode_uint32 (fp, n_persistent) == -1)
290                 goto exit;
291
292         /* Write a tag + value pair for each persistent property.
293          * Tags identify the property ID and data type; they're an
294          * artifact of CamelArgs.  The persistent GObject property
295          * IDs are set to match the legacy CamelArg tag values. */
296
297         for (ii = 0; ii < n_properties; ii++) {
298                 GParamSpec *pspec = properties[ii];
299                 guint32 tag, v_uint32;
300
301                 if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
302                         continue;
303
304                 g_value_init (&value, pspec->value_type);
305
306                 g_object_get_property (
307                         G_OBJECT (object), pspec->name, &value);
308
309                 tag = pspec->param_id;
310
311                 /* Record the GValue to the state file.
312                  * XXX We currently only support booleans. */
313                 switch (pspec->value_type) {
314                         case G_TYPE_BOOLEAN:
315                                 tag |= CAMEL_ARG_BOO;
316                                 v_uint32 = g_value_get_boolean (&value);
317                                 if (camel_file_util_encode_uint32 (fp, tag) == -1)
318                                         goto exit;
319                                 if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
320                                         goto exit;
321                                 break;
322                         default:
323                                 g_warn_if_reached ();
324                                 goto exit;
325                 }
326
327                 g_value_unset (&value);
328         }
329
330 exit:
331         g_free (properties);
332
333         return 0;
334 }
335
336 static void
337 camel_object_class_init (CamelObjectClass *class)
338 {
339         GObjectClass *object_class;
340
341         g_type_class_add_private (class, sizeof (CamelObjectPrivate));
342
343         object_class = G_OBJECT_CLASS (class);
344         object_class->set_property = object_set_property;
345         object_class->get_property = object_get_property;
346         object_class->finalize = object_finalize;
347
348         class->state_read = object_state_read;
349         class->state_write = object_state_write;
350
351         /**
352          * CamelObject:state-filename
353          *
354          * The file in which to store persistent property values for this
355          * instance.
356          **/
357         g_object_class_install_property (
358                 object_class,
359                 PROP_STATE_FILENAME,
360                 g_param_spec_string (
361                         "state-filename",
362                         "State Filename",
363                         "File containing persistent property values",
364                         NULL,
365                         G_PARAM_READWRITE |
366                         G_PARAM_CONSTRUCT));
367 }
368
369 static void
370 camel_object_init (CamelObject *object)
371 {
372         object->priv = CAMEL_OBJECT_GET_PRIVATE (object);
373 }
374
375 G_DEFINE_QUARK (camel-error-quark, camel_error)
376
377 /**
378  * camel_object_state_read:
379  * @object: a #CamelObject
380  *
381  * Read persistent object state from #CamelObject:state-filename.
382  *
383  * Returns: -1 on error.
384  **/
385 gint
386 camel_object_state_read (CamelObject *object)
387 {
388         CamelObjectClass *class;
389         const gchar *state_filename;
390         gint res = -1;
391         FILE *fp;
392         gchar magic[4];
393
394         g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
395
396         class = CAMEL_OBJECT_GET_CLASS (object);
397
398         state_filename = camel_object_get_state_filename (object);
399         if (state_filename == NULL)
400                 return 0;
401
402         fp = g_fopen (state_filename, "rb");
403         if (fp != NULL) {
404                 if (fread (magic, 4, 1, fp) == 1
405                     && memcmp (magic, CAMEL_OBJECT_STATE_FILE_MAGIC, 4) == 0)
406                         res = class->state_read (object, fp);
407                 fclose (fp);
408         }
409
410         return res;
411 }
412
413 /**
414  * camel_object_state_write:
415  * @object: a #CamelObject
416  *
417  * Write persistent object state #CamelObject:state-filename.
418  *
419  * Returns: -1 on error.
420  **/
421 gint
422 camel_object_state_write (CamelObject *object)
423 {
424         CamelObjectClass *class;
425         const gchar *state_filename;
426         gchar *savename, *dirname;
427         gint res = -1;
428         FILE *fp;
429
430         g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
431
432         class = CAMEL_OBJECT_GET_CLASS (object);
433
434         state_filename = camel_object_get_state_filename (object);
435         if (state_filename == NULL)
436                 return 0;
437
438         savename = camel_file_util_savename (state_filename);
439
440         dirname = g_path_get_dirname (savename);
441         g_mkdir_with_parents (dirname, 0700);
442         g_free (dirname);
443
444         fp = g_fopen (savename, "wb");
445         if (fp != NULL) {
446                 if (fwrite (CAMEL_OBJECT_STATE_FILE_MAGIC, 4, 1, fp) == 1
447                     && class->state_write (object, fp) == 0) {
448                         if (fclose (fp) == 0) {
449                                 res = 0;
450                                 g_rename (savename, state_filename);
451                         }
452                 } else {
453                         fclose (fp);
454                 }
455         } else {
456                 g_warning ("Could not save object state file to '%s': %s", savename, g_strerror (errno));
457         }
458
459         g_free (savename);
460
461         return res;
462 }
463
464 /**
465  * camel_object_get_state_filename:
466  * @object: a #CamelObject
467  *
468  * Returns the name of the file in which persistent property values for
469  * @object are stored.  The file is used by camel_object_state_write()
470  * and camel_object_state_read() to save and restore object state.
471  *
472  * Returns: the name of the persistent property file
473  *
474  * Since: 2.32
475  **/
476 const gchar *
477 camel_object_get_state_filename (CamelObject *object)
478 {
479         g_return_val_if_fail (CAMEL_IS_OBJECT (object), NULL);
480
481         return object->priv->state_filename;
482 }
483
484 /**
485  * camel_object_set_state_filename:
486  * @object: a #CamelObject
487  * @state_filename: path to a local file
488  *
489  * Sets the name of the file in which persistent property values for
490  * @object are stored.  The file is used by camel_object_state_write()
491  * and camel_object_state_read() to save and restore object state.
492  *
493  * Since: 2.32
494  **/
495 void
496 camel_object_set_state_filename (CamelObject *object,
497                                  const gchar *state_filename)
498 {
499         g_return_if_fail (CAMEL_IS_OBJECT (object));
500
501         if (g_strcmp0 (object->priv->state_filename, state_filename) == 0)
502                 return;
503
504         g_free (object->priv->state_filename);
505         object->priv->state_filename = g_strdup (state_filename);
506
507         g_object_notify (G_OBJECT (object), "state-filename");
508 }