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