/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "camel-file-utils.h"
#include "camel-object.h"
#define d(x)
#define CAMEL_OBJECT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_OBJECT, CamelObjectPrivate))
struct _CamelObjectPrivate {
gchar *state_filename;
};
enum {
PROP_0,
PROP_STATE_FILENAME
};
G_DEFINE_ABSTRACT_TYPE (CamelObject, camel_object, G_TYPE_OBJECT)
/* State file for CamelObject data.
* Any later versions should only append data.
*
* version:uint32
*
* Version 0 of the file:
*
* version:uint32 = 0
* count:uint32 -- count of meta-data items
* ( name:string value:string ) *count -- meta-data items
*
* Version 1 of the file adds:
* count:uint32 -- count of persistent properties
* ( tag:uing32 value:tagtype ) *count -- persistent properties
*/
#define CAMEL_OBJECT_STATE_FILE_MAGIC "CLMD"
/* XXX This is a holdover from Camel's old homegrown type system.
* CamelArg was a kind of primitive version of GObject properties.
* The argument ID and data type were encoded into a 32-bit integer.
* Unfortunately the encoding was also used in the binary state file
* format, so we still need the secret decoder ring. */
enum camel_arg_t {
CAMEL_ARG_END = 0,
CAMEL_ARG_IGNORE = 1, /* override/ignore an arg in-place */
CAMEL_ARG_FIRST = 1024, /* 1024 args reserved for arg system */
CAMEL_ARG_TYPE = 0xf0000000, /* type field for tags */
CAMEL_ARG_TAG = 0x0fffffff, /* tag field for args */
CAMEL_ARG_OBJ = 0x00000000, /* object */
CAMEL_ARG_INT = 0x10000000, /* gint */
CAMEL_ARG_DBL = 0x20000000, /* gdouble */
CAMEL_ARG_STR = 0x30000000, /* c string */
CAMEL_ARG_PTR = 0x40000000, /* ptr */
CAMEL_ARG_BOO = 0x50000000 /* bool */
};
#define CAMEL_ARGV_MAX (20)
static void
object_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_STATE_FILENAME:
camel_object_set_state_filename (
CAMEL_OBJECT (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
object_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_STATE_FILENAME:
g_value_set_string (
value, camel_object_get_state_filename (
CAMEL_OBJECT (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
object_finalize (GObject *object)
{
CamelObjectPrivate *priv;
priv = CAMEL_OBJECT_GET_PRIVATE (object);
g_free (priv->state_filename);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_object_parent_class)->finalize (object);
}
static void
object_notify (GObject *object,
GParamSpec *pspec)
{
/* Placeholder so subclasses can safely chain up, since
* GObjectClass itself does not implement this method. */
}
static gint
object_state_read (CamelObject *object,
FILE *fp)
{
GValue value = G_VALUE_INIT;
GObjectClass *class;
GParamSpec **properties;
guint32 count, version;
guint ii, jj, n_properties;
if (camel_file_util_decode_uint32 (fp, &version) == -1)
return -1;
if (version > 1)
return -1;
if (camel_file_util_decode_uint32 (fp, &count) == -1)
return -1;
/* XXX Camel no longer supports meta-data in state
* files, so we're just eating dead data here. */
for (ii = 0; ii < count; ii++) {
gchar *name = NULL;
gchar *value = NULL;
gboolean success;
success =
camel_file_util_decode_string (fp, &name) == 0 &&
camel_file_util_decode_string (fp, &value) == 0;
g_free (name);
g_free (value);
if (!success)
return -1;
}
if (version == 0)
return 0;
if (camel_file_util_decode_uint32 (fp, &count) == -1)
return 0;
if (count == 0 || count > 1024)
/* Maybe it was just version 0 afterall. */
return 0;
count = MIN (count, CAMEL_ARGV_MAX);
class = G_OBJECT_GET_CLASS (object);
properties = g_object_class_list_properties (class, &n_properties);
for (ii = 0; ii < count; ii++) {
gboolean property_set = FALSE;
guint32 tag, v_uint32;
if (camel_file_util_decode_uint32 (fp, &tag) == -1)
goto exit;
/* Record state file values into GValues.
* XXX We currently only support booleans. */
switch (tag & CAMEL_ARG_TYPE) {
case CAMEL_ARG_BOO:
if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
goto exit;
g_value_init (&value, G_TYPE_BOOLEAN);
g_value_set_boolean (&value, (gboolean) v_uint32);
break;
default:
g_warn_if_reached ();
goto exit;
}
/* Now we have to match the legacy numeric CamelArg tag
* value with a GObject property. The GObject property
* IDs have been set to the same legacy tag values, but
* we have to access a private GParamSpec field to get
* to them (pspec->param_id). */
tag &= CAMEL_ARG_TAG; /* filter out the type code */
for (jj = 0; jj < n_properties; jj++) {
GParamSpec *pspec = properties[jj];
if (pspec->param_id != tag)
continue;
/* Sanity check. */
g_warn_if_fail (pspec->flags & CAMEL_PARAM_PERSISTENT);
if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
continue;
g_object_set_property (
G_OBJECT (object), pspec->name, &value);
property_set = TRUE;
break;
}
/* XXX This tag was used by the old IMAP backend.
* It may still show up in accounts that were
* migrated from IMAP to IMAPX. Silence the
* warning. */
if (tag == 0x2500)
property_set = TRUE;
if (!property_set)
g_warning (
"Could not find a corresponding %s "
"property for state file tag 0x%x",
G_OBJECT_TYPE_NAME (object), tag);
g_value_unset (&value);
}
exit:
g_free (properties);
return 0;
}
static gint
object_state_write (CamelObject *object,
FILE *fp)
{
GValue value = G_VALUE_INIT;
GObjectClass *class;
GParamSpec **properties;
guint ii, n_properties;
guint32 n_persistent = 0;
class = G_OBJECT_GET_CLASS (object);
properties = g_object_class_list_properties (class, &n_properties);
/* Version = 1 */
if (camel_file_util_encode_uint32 (fp, 1) == -1)
goto exit;
/* No meta-data items. */
if (camel_file_util_encode_uint32 (fp, 0) == -1)
goto exit;
/* Count persistent properties. */
for (ii = 0; ii < n_properties; ii++)
if (properties[ii]->flags & CAMEL_PARAM_PERSISTENT)
n_persistent++;
if (camel_file_util_encode_uint32 (fp, n_persistent) == -1)
goto exit;
/* Write a tag + value pair for each persistent property.
* Tags identify the property ID and data type; they're an
* artifact of CamelArgs. The persistent GObject property
* IDs are set to match the legacy CamelArg tag values. */
for (ii = 0; ii < n_properties; ii++) {
GParamSpec *pspec = properties[ii];
guint32 tag, v_uint32;
if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
continue;
g_value_init (&value, pspec->value_type);
g_object_get_property (
G_OBJECT (object), pspec->name, &value);
tag = pspec->param_id;
/* Record the GValue to the state file.
* XXX We currently only support booleans. */
switch (pspec->value_type) {
case G_TYPE_BOOLEAN:
tag |= CAMEL_ARG_BOO;
v_uint32 = g_value_get_boolean (&value);
if (camel_file_util_encode_uint32 (fp, tag) == -1)
goto exit;
if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
goto exit;
break;
default:
g_warn_if_reached ();
goto exit;
}
g_value_unset (&value);
}
exit:
g_free (properties);
return 0;
}
static void
camel_object_class_init (CamelObjectClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (CamelObjectPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = object_set_property;
object_class->get_property = object_get_property;
object_class->finalize = object_finalize;
object_class->notify = object_notify;
class->state_read = object_state_read;
class->state_write = object_state_write;
/**
* CamelObject:state-filename
*
* The file in which to store persistent property values for this
* instance.
**/
g_object_class_install_property (
object_class,
PROP_STATE_FILENAME,
g_param_spec_string (
"state-filename",
"State Filename",
"File containing persistent property values",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
camel_object_init (CamelObject *object)
{
object->priv = CAMEL_OBJECT_GET_PRIVATE (object);
}
G_DEFINE_QUARK (camel-error-quark, camel_error)
/**
* camel_object_state_read:
* @object: a #CamelObject
*
* Read persistent object state from #CamelObject:state-filename.
*
* Returns: -1 on error.
**/
gint
camel_object_state_read (CamelObject *object)
{
CamelObjectClass *class;
const gchar *state_filename;
gint res = -1;
FILE *fp;
gchar magic[4];
g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
class = CAMEL_OBJECT_GET_CLASS (object);
state_filename = camel_object_get_state_filename (object);
if (state_filename == NULL)
return 0;
fp = g_fopen (state_filename, "rb");
if (fp != NULL) {
if (fread (magic, 4, 1, fp) == 1
&& memcmp (magic, CAMEL_OBJECT_STATE_FILE_MAGIC, 4) == 0)
res = class->state_read (object, fp);
fclose (fp);
}
return res;
}
/**
* camel_object_state_write:
* @object: a #CamelObject
*
* Write persistent object state #CamelObject:state-filename.
*
* Returns: -1 on error.
**/
gint
camel_object_state_write (CamelObject *object)
{
CamelObjectClass *class;
const gchar *state_filename;
gchar *savename, *dirname;
gint res = -1;
FILE *fp;
g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
class = CAMEL_OBJECT_GET_CLASS (object);
state_filename = camel_object_get_state_filename (object);
if (state_filename == NULL)
return 0;
savename = camel_file_util_savename (state_filename);
dirname = g_path_get_dirname (savename);
g_mkdir_with_parents (dirname, 0700);
g_free (dirname);
fp = g_fopen (savename, "wb");
if (fp != NULL) {
if (fwrite (CAMEL_OBJECT_STATE_FILE_MAGIC, 4, 1, fp) == 1
&& class->state_write (object, fp) == 0) {
if (fclose (fp) == 0) {
res = 0;
if (g_rename (savename, state_filename) == -1)
res = -1;
}
} else {
fclose (fp);
}
} else {
g_warning ("Could not save object state file to '%s': %s", savename, g_strerror (errno));
}
g_free (savename);
return res;
}
/**
* camel_object_get_state_filename:
* @object: a #CamelObject
*
* Returns the name of the file in which persistent property values for
* @object are stored. The file is used by camel_object_state_write()
* and camel_object_state_read() to save and restore object state.
*
* Returns: the name of the persistent property file
*
* Since: 2.32
**/
const gchar *
camel_object_get_state_filename (CamelObject *object)
{
g_return_val_if_fail (CAMEL_IS_OBJECT (object), NULL);
return object->priv->state_filename;
}
/**
* camel_object_set_state_filename:
* @object: a #CamelObject
* @state_filename: path to a local file
*
* Sets the name of the file in which persistent property values for
* @object are stored. The file is used by camel_object_state_write()
* and camel_object_state_read() to save and restore object state.
*
* Since: 2.32
**/
void
camel_object_set_state_filename (CamelObject *object,
const gchar *state_filename)
{
g_return_if_fail (CAMEL_IS_OBJECT (object));
if (g_strcmp0 (object->priv->state_filename, state_filename) == 0)
return;
g_free (object->priv->state_filename);
object->priv->state_filename = g_strdup (state_filename);
g_object_notify (G_OBJECT (object), "state-filename");
}