
2001-10-16 Jeffrey Stedfast <fejj@ximian.com> * camel-object.[c,h]: If CAMEL_DEBUG is defined, print some useful ref/unref info. * providers/imap/camel-imap-store.c (delete_folder): Fixed an assignment warning. * camel-uid-cache.c (camel_uid_cache_new): Make sure that the parent directory exists before trying to open the filename, if it doesn't, create it. svn path=/trunk/; revision=13707
1107 lines
28 KiB
C
1107 lines
28 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/* camel-object.c: Base class for Camel */
|
|
|
|
/*
|
|
* Author:
|
|
* Dan Winship <danw@ximian.com>
|
|
*
|
|
* Copyright 2000 Ximian, Inc. (www.ximian.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; 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
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include "camel-object.h"
|
|
|
|
#ifdef ENABLE_THREADS
|
|
#include <pthread.h>
|
|
#include <e-util/e-msgport.h>
|
|
#endif
|
|
|
|
/* I just mashed the keyboard for these... */
|
|
#define CAMEL_OBJECT_MAGIC_VALUE 0x77A344EF
|
|
#define CAMEL_OBJECT_CLASS_MAGIC_VALUE 0xEE26A990
|
|
#define CAMEL_OBJECT_FINALIZED_VALUE 0x84AC3656
|
|
#define CAMEL_OBJECT_CLASS_FINALIZED_VALUE 0x7621ABCD
|
|
|
|
#define DEFAULT_PREALLOCS 8
|
|
|
|
#define BAST_CASTARD 1 /* Define to return NULL when casts fail */
|
|
|
|
#define NULL_PREP_VALUE ((gpointer)make_global_classfuncs) /* See camel_object_class_declare_event */
|
|
|
|
/* ** Quickie type system ************************************************* */
|
|
|
|
typedef struct _CamelTypeInfo
|
|
{
|
|
CamelType self;
|
|
CamelType parent;
|
|
const gchar *name;
|
|
|
|
size_t instance_size;
|
|
GMemChunk *instance_chunk;
|
|
CamelObjectInitFunc instance_init;
|
|
CamelObjectFinalizeFunc instance_finalize;
|
|
GList *free_instances;
|
|
|
|
size_t classfuncs_size;
|
|
CamelObjectClassInitFunc class_init;
|
|
CamelObjectClassFinalizeFunc class_finalize;
|
|
CamelObjectClass *global_classfuncs;
|
|
}
|
|
CamelTypeInfo;
|
|
|
|
/* A 'locked' hooklist, that is only allocated on demand */
|
|
typedef struct _CamelHookList {
|
|
EMutex *lock;
|
|
|
|
unsigned int depth:30; /* recursive event depth */
|
|
unsigned int flags:2; /* flags, see below */
|
|
|
|
unsigned int list_length;
|
|
struct _CamelHookPair *list;
|
|
} CamelHookList;
|
|
|
|
#define CAMEL_HOOK_PAIR_REMOVED (1<<0)
|
|
|
|
/* a 'hook pair', actually a hook tuple, we just store all hooked events in the same list,
|
|
and just comapre as we go, rather than storing separate lists for each hook type
|
|
|
|
the name field just points directly to the key field in the class's preplist hashtable.
|
|
This way we can just use a direct pointer compare when scanning it, and also saves
|
|
copying the string */
|
|
typedef struct _CamelHookPair
|
|
{
|
|
struct _CamelHookPair *next; /* next MUST be the first member */
|
|
|
|
unsigned int flags; /* removed, etc */
|
|
|
|
const char *name; /* points to the key field in the classes preplist, static memory */
|
|
CamelObjectEventHookFunc func;
|
|
void *data;
|
|
} CamelHookPair;
|
|
|
|
/* ************************************************************************ */
|
|
|
|
static void camel_type_lock_up (void);
|
|
static void camel_type_lock_down (void);
|
|
|
|
static void obj_init (CamelObject * obj);
|
|
static void obj_finalize (CamelObject * obj);
|
|
static void obj_class_init (CamelObjectClass * class);
|
|
static void obj_class_finalize (CamelObjectClass * class);
|
|
|
|
static gboolean shared_is_of_type (CamelObjectShared * sh, CamelType ctype,
|
|
gboolean is_obj);
|
|
static void make_global_classfuncs (CamelTypeInfo * type_info);
|
|
|
|
static void camel_object_free_hooks(CamelObject *o);
|
|
|
|
/* ************************************************************************ */
|
|
|
|
G_LOCK_DEFINE_STATIC (type_system);
|
|
G_LOCK_DEFINE_STATIC (type_system_level);
|
|
static GPrivate *type_system_locklevel = NULL;
|
|
|
|
G_LOCK_DEFINE_STATIC (refcount);
|
|
|
|
static gboolean type_system_initialized = FALSE;
|
|
static GHashTable *ctype_to_typeinfo = NULL;
|
|
static const CamelType camel_object_type = 1;
|
|
static CamelType cur_max_type = CAMEL_INVALID_TYPE;
|
|
|
|
/* ************************************************************************ */
|
|
|
|
#define LOCK_VAL (GPOINTER_TO_INT (g_private_get (type_system_locklevel)))
|
|
#define LOCK_SET( val ) g_private_set (type_system_locklevel, GINT_TO_POINTER (val))
|
|
|
|
static void
|
|
camel_type_lock_up (void)
|
|
{
|
|
G_LOCK (type_system_level);
|
|
|
|
if (type_system_locklevel == NULL)
|
|
type_system_locklevel = g_private_new (GINT_TO_POINTER (0));
|
|
|
|
if (LOCK_VAL == 0) {
|
|
G_UNLOCK (type_system_level);
|
|
G_LOCK (type_system);
|
|
G_LOCK (type_system_level);
|
|
}
|
|
|
|
LOCK_SET (LOCK_VAL + 1);
|
|
|
|
G_UNLOCK (type_system_level);
|
|
}
|
|
|
|
static void
|
|
camel_type_lock_down (void)
|
|
{
|
|
G_LOCK (type_system_level);
|
|
|
|
if (type_system_locklevel == NULL) {
|
|
g_warning
|
|
("camel_type_lock_down: lock down before a lock up?");
|
|
type_system_locklevel = g_private_new (GINT_TO_POINTER (0));
|
|
G_UNLOCK (type_system_level);
|
|
return;
|
|
}
|
|
|
|
LOCK_SET (LOCK_VAL - 1);
|
|
|
|
if (LOCK_VAL == 0)
|
|
G_UNLOCK (type_system);
|
|
|
|
G_UNLOCK (type_system_level);
|
|
}
|
|
|
|
void
|
|
camel_type_init (void)
|
|
{
|
|
CamelTypeInfo *obj_info;
|
|
|
|
camel_type_lock_up ();
|
|
|
|
if (type_system_initialized) {
|
|
g_warning
|
|
("camel_type_init: type system already initialized.");
|
|
camel_type_lock_down ();
|
|
return;
|
|
}
|
|
|
|
type_system_initialized = TRUE;
|
|
ctype_to_typeinfo = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
obj_info = g_new (CamelTypeInfo, 1);
|
|
obj_info->self = camel_object_type;
|
|
obj_info->parent = CAMEL_INVALID_TYPE;
|
|
obj_info->name = "CamelObject";
|
|
|
|
obj_info->instance_size = sizeof (CamelObject);
|
|
obj_info->instance_chunk =
|
|
g_mem_chunk_create (CamelObject, DEFAULT_PREALLOCS,
|
|
G_ALLOC_ONLY);
|
|
obj_info->instance_init = obj_init;
|
|
obj_info->instance_finalize = obj_finalize;
|
|
obj_info->free_instances = NULL;
|
|
|
|
obj_info->classfuncs_size = sizeof (CamelObjectClass);
|
|
obj_info->class_init = obj_class_init;
|
|
obj_info->class_finalize = obj_class_finalize;
|
|
|
|
g_hash_table_insert (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (CAMEL_INVALID_TYPE), NULL);
|
|
g_hash_table_insert (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (camel_object_type), obj_info);
|
|
|
|
/* Sigh. Ugly */
|
|
make_global_classfuncs (obj_info);
|
|
|
|
cur_max_type = camel_object_type;
|
|
|
|
camel_type_lock_down ();
|
|
}
|
|
|
|
CamelType
|
|
camel_type_register (CamelType parent, const gchar * name,
|
|
size_t instance_size, size_t classfuncs_size,
|
|
CamelObjectClassInitFunc class_init,
|
|
CamelObjectClassFinalizeFunc class_finalize,
|
|
CamelObjectInitFunc instance_init,
|
|
CamelObjectFinalizeFunc instance_finalize)
|
|
{
|
|
CamelTypeInfo *parent_info;
|
|
CamelTypeInfo *obj_info;
|
|
gchar *chunkname;
|
|
|
|
g_return_val_if_fail (parent != CAMEL_INVALID_TYPE,
|
|
CAMEL_INVALID_TYPE);
|
|
g_return_val_if_fail (name, CAMEL_INVALID_TYPE);
|
|
g_return_val_if_fail (instance_size, CAMEL_INVALID_TYPE);
|
|
g_return_val_if_fail (classfuncs_size, CAMEL_INVALID_TYPE);
|
|
|
|
camel_type_lock_up ();
|
|
|
|
if (type_system_initialized == FALSE) {
|
|
G_UNLOCK (type_system);
|
|
camel_type_init ();
|
|
G_LOCK (type_system);
|
|
}
|
|
|
|
parent_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (parent));
|
|
|
|
if (parent_info == NULL) {
|
|
g_warning
|
|
("camel_type_register: no such parent type %d of class `%s'",
|
|
parent, name);
|
|
camel_type_lock_down ();
|
|
return CAMEL_INVALID_TYPE;
|
|
}
|
|
|
|
if (parent_info->instance_size > instance_size) {
|
|
g_warning
|
|
("camel_type_register: instance of class `%s' would be smaller than parent `%s'",
|
|
name, parent_info->name);
|
|
camel_type_lock_down ();
|
|
return CAMEL_INVALID_TYPE;
|
|
}
|
|
|
|
if (parent_info->classfuncs_size > classfuncs_size) {
|
|
g_warning
|
|
("camel_type_register: classfuncs of class `%s' would be smaller than parent `%s'",
|
|
name, parent_info->name);
|
|
camel_type_lock_down ();
|
|
return CAMEL_INVALID_TYPE;
|
|
}
|
|
|
|
cur_max_type++;
|
|
|
|
obj_info = g_new (CamelTypeInfo, 1);
|
|
obj_info->self = cur_max_type;
|
|
obj_info->parent = parent;
|
|
obj_info->name = name;
|
|
|
|
obj_info->instance_size = instance_size;
|
|
chunkname =
|
|
g_strdup_printf ("chunk for instances of Camel type `%s'",
|
|
name);
|
|
obj_info->instance_chunk =
|
|
g_mem_chunk_new (chunkname, instance_size,
|
|
instance_size * DEFAULT_PREALLOCS,
|
|
G_ALLOC_ONLY);
|
|
g_free (chunkname);
|
|
obj_info->instance_init = instance_init;
|
|
obj_info->instance_finalize = instance_finalize;
|
|
obj_info->free_instances = NULL;
|
|
|
|
obj_info->classfuncs_size = classfuncs_size;
|
|
obj_info->class_init = class_init;
|
|
obj_info->class_finalize = class_finalize;
|
|
|
|
g_hash_table_insert (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (obj_info->self), obj_info);
|
|
|
|
/* Sigh. Ugly. */
|
|
make_global_classfuncs (obj_info);
|
|
|
|
camel_type_lock_down ();
|
|
return obj_info->self;
|
|
}
|
|
|
|
CamelObjectClass *
|
|
camel_type_get_global_classfuncs (CamelType type)
|
|
{
|
|
CamelTypeInfo *type_info;
|
|
|
|
g_return_val_if_fail (type != CAMEL_INVALID_TYPE, NULL);
|
|
|
|
camel_type_lock_up ();
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type));
|
|
camel_type_lock_down ();
|
|
|
|
g_return_val_if_fail (type_info != NULL, NULL);
|
|
|
|
return type_info->global_classfuncs;
|
|
}
|
|
|
|
const gchar *
|
|
camel_type_to_name (CamelType type)
|
|
{
|
|
CamelTypeInfo *type_info;
|
|
|
|
g_return_val_if_fail (type != CAMEL_INVALID_TYPE,
|
|
"(the invalid type)");
|
|
|
|
camel_type_lock_up ();
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type));
|
|
camel_type_lock_down ();
|
|
|
|
g_return_val_if_fail (type_info != NULL,
|
|
"(a bad type parameter was specified)");
|
|
|
|
return type_info->name;
|
|
}
|
|
|
|
/* ** The CamelObject ***************************************************** */
|
|
|
|
static void
|
|
obj_init (CamelObject * obj)
|
|
{
|
|
obj->s.magic = CAMEL_OBJECT_MAGIC_VALUE;
|
|
obj->ref_count = 1;
|
|
obj->hooks = NULL;
|
|
obj->in_event = 0;
|
|
obj->destroying = 0;
|
|
}
|
|
|
|
static void
|
|
obj_finalize (CamelObject * obj)
|
|
{
|
|
g_return_if_fail (obj->s.magic == CAMEL_OBJECT_MAGIC_VALUE);
|
|
g_return_if_fail (obj->ref_count == 0);
|
|
g_return_if_fail (obj->in_event == 0);
|
|
|
|
obj->s.magic = CAMEL_OBJECT_FINALIZED_VALUE;
|
|
|
|
camel_object_free_hooks(obj);
|
|
}
|
|
|
|
static void
|
|
obj_class_init (CamelObjectClass * class)
|
|
{
|
|
class->s.magic = CAMEL_OBJECT_CLASS_MAGIC_VALUE;
|
|
|
|
camel_object_class_declare_event (class, "finalize", NULL);
|
|
}
|
|
|
|
static void
|
|
obj_class_finalize (CamelObjectClass * class)
|
|
{
|
|
g_return_if_fail (class->s.magic == CAMEL_OBJECT_CLASS_MAGIC_VALUE);
|
|
|
|
class->s.magic = CAMEL_OBJECT_CLASS_FINALIZED_VALUE;
|
|
|
|
if (class->event_to_preplist) {
|
|
/* FIXME: This leaks the preplist slist entries */
|
|
g_hash_table_foreach (class->event_to_preplist,
|
|
(GHFunc) g_free, NULL);
|
|
g_hash_table_destroy (class->event_to_preplist);
|
|
class->event_to_preplist = NULL;
|
|
}
|
|
}
|
|
|
|
CamelType
|
|
camel_object_get_type (void)
|
|
{
|
|
if (type_system_initialized == FALSE)
|
|
camel_type_init ();
|
|
|
|
return camel_object_type;
|
|
}
|
|
|
|
CamelObject *
|
|
camel_object_new (CamelType type)
|
|
{
|
|
CamelTypeInfo *type_info;
|
|
GSList *parents = NULL;
|
|
GSList *head = NULL;
|
|
CamelObject *instance;
|
|
|
|
g_return_val_if_fail (type != CAMEL_INVALID_TYPE, NULL);
|
|
|
|
/* Look up the type */
|
|
|
|
camel_type_lock_up ();
|
|
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type));
|
|
|
|
if (type_info == NULL) {
|
|
g_warning
|
|
("camel_object_new: trying to create object of invalid type %d",
|
|
type);
|
|
camel_type_lock_down ();
|
|
return NULL;
|
|
}
|
|
|
|
/* Grab an instance out of the freed ones if possible, alloc otherwise */
|
|
|
|
if (type_info->free_instances) {
|
|
GList *first;
|
|
|
|
first = g_list_first (type_info->free_instances);
|
|
instance = first->data;
|
|
type_info->free_instances =
|
|
g_list_remove_link (type_info->free_instances, first);
|
|
g_list_free_1 (first);
|
|
memset (instance, 0, type_info->instance_size);
|
|
} else {
|
|
instance = g_mem_chunk_alloc0 (type_info->instance_chunk);
|
|
}
|
|
|
|
/* Init the instance and classfuncs a bit */
|
|
|
|
instance->s.type = type;
|
|
instance->classfuncs = type_info->global_classfuncs;
|
|
|
|
/* Loop through the parents in simplest -> most complex order, initing the class and instance.
|
|
|
|
* When parent = CAMEL_INVALID_TYPE and we're at the end of the line, _lookup returns NULL
|
|
* because we inserted it as corresponding to CAMEL_INVALID_TYPE. Clever, eh?
|
|
*/
|
|
|
|
while (type_info) {
|
|
parents = g_slist_prepend (parents, type_info);
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type_info->
|
|
parent));
|
|
}
|
|
|
|
head = parents;
|
|
|
|
for (; parents && parents->data; parents = parents->next) {
|
|
CamelTypeInfo *thisinfo;
|
|
|
|
thisinfo = parents->data;
|
|
if (thisinfo->instance_init)
|
|
(thisinfo->instance_init) (instance);
|
|
}
|
|
|
|
g_slist_free (head);
|
|
|
|
camel_type_lock_down ();
|
|
return instance;
|
|
}
|
|
|
|
#ifdef camel_object_ref
|
|
#undef camel_object_ref
|
|
#endif
|
|
|
|
void
|
|
camel_object_ref (CamelObject * obj)
|
|
{
|
|
g_return_if_fail (CAMEL_IS_OBJECT (obj));
|
|
|
|
G_LOCK (refcount);
|
|
obj->ref_count++;
|
|
G_UNLOCK (refcount);
|
|
}
|
|
|
|
#ifdef camel_object_unref
|
|
#undef camel_object_unref
|
|
#endif
|
|
|
|
void
|
|
camel_object_unref (CamelObject * obj)
|
|
{
|
|
CamelTypeInfo *type_info;
|
|
CamelTypeInfo *iter;
|
|
GSList *parents = NULL;
|
|
GSList *head = NULL;
|
|
|
|
g_return_if_fail (CAMEL_IS_OBJECT (obj));
|
|
|
|
G_LOCK (refcount);
|
|
obj->ref_count--;
|
|
|
|
if (obj->ref_count > 0) {
|
|
G_UNLOCK (refcount);
|
|
return;
|
|
}
|
|
|
|
G_UNLOCK (refcount);
|
|
|
|
/* If the object already had its last unref, do not begin the
|
|
* destruction process again. This can happen if, for example,
|
|
* the object sends an event in its finalize handler (vfolders
|
|
* do this).
|
|
*/
|
|
|
|
if (obj->destroying)
|
|
return;
|
|
|
|
obj->destroying = 1;
|
|
|
|
/* Send the finalize event */
|
|
|
|
camel_object_trigger_event (obj, "finalize", NULL);
|
|
|
|
/* Destroy it! hahaha! */
|
|
|
|
camel_type_lock_up ();
|
|
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (obj->s.type));
|
|
|
|
if (type_info == NULL) {
|
|
g_warning
|
|
("camel_object_unref: seemingly valid object has a bad type %d",
|
|
obj->s.type);
|
|
camel_type_lock_down ();
|
|
return;
|
|
}
|
|
|
|
/* Loop through the parents in most complex -> simplest order, finalizing the class
|
|
* and instance.
|
|
*
|
|
* When parent = CAMEL_INVALID_TYPE and we're at the end of the line, _lookup returns NULL
|
|
* because we inserted it as corresponding to CAMEL_INVALID_TYPE. Clever, eh?
|
|
*
|
|
* Use iter to preserve type_info for free_{instance,classfunc}s
|
|
*/
|
|
|
|
iter = type_info;
|
|
|
|
while (iter) {
|
|
parents = g_slist_prepend (parents, iter);
|
|
iter =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (iter->parent));
|
|
}
|
|
|
|
/* ok, done with the type stuff, and our data pointers
|
|
* won't go bad. */
|
|
camel_type_lock_down ();
|
|
|
|
parents = g_slist_reverse (parents);
|
|
head = parents;
|
|
|
|
for (; parents && parents->data; parents = parents->next) {
|
|
CamelTypeInfo *thisinfo;
|
|
|
|
thisinfo = parents->data;
|
|
if (thisinfo->instance_finalize)
|
|
(thisinfo->instance_finalize) (obj);
|
|
}
|
|
|
|
g_slist_free (head);
|
|
|
|
/* Sanity check */
|
|
|
|
if (obj->ref_count != 0)
|
|
g_warning ("camel_object_unref: destroyed object %s at %p somehow got"
|
|
" referenced in destruction chain.",
|
|
camel_type_to_name (obj->s.type),
|
|
obj);
|
|
|
|
/* A little bit of cleaning up.
|
|
|
|
* Don't erase the type, so we can peek at it if a finalized object
|
|
* is check_cast'ed somewhere. Fill it with gunk to help detect
|
|
* other invalid ref's of it.
|
|
*/
|
|
|
|
memset (obj, 0xEB, type_info->instance_size);
|
|
obj->s.type = type_info->self;
|
|
obj->s.magic = CAMEL_OBJECT_FINALIZED_VALUE;
|
|
|
|
/* Tuck away the pointer for use in a new object */
|
|
|
|
camel_type_lock_up ();
|
|
|
|
type_info->free_instances =
|
|
g_list_prepend (type_info->free_instances, obj);
|
|
|
|
camel_type_lock_down ();
|
|
}
|
|
|
|
gboolean
|
|
camel_object_is_of_type (CamelObject * obj, CamelType ctype)
|
|
{
|
|
return shared_is_of_type ((CamelObjectShared *) obj, ctype, TRUE);
|
|
}
|
|
|
|
gboolean
|
|
camel_object_class_is_of_type (CamelObjectClass * class, CamelType ctype)
|
|
{
|
|
return shared_is_of_type ((CamelObjectShared *) class, ctype, FALSE);
|
|
}
|
|
|
|
#ifdef BAST_CASTARD
|
|
#define ERRVAL NULL
|
|
#else
|
|
#define ERRVAL obj
|
|
#endif
|
|
|
|
CamelObject *
|
|
camel_object_check_cast (CamelObject * obj, CamelType ctype)
|
|
{
|
|
if (shared_is_of_type ((CamelObjectShared *) obj, ctype, TRUE))
|
|
return obj;
|
|
return ERRVAL;
|
|
}
|
|
|
|
CamelObjectClass *
|
|
camel_object_class_check_cast (CamelObjectClass * class, CamelType ctype)
|
|
{
|
|
if (shared_is_of_type ((CamelObjectShared *) class, ctype, FALSE))
|
|
return class;
|
|
return ERRVAL;
|
|
}
|
|
|
|
#undef ERRVAL
|
|
|
|
gchar *
|
|
camel_object_describe (CamelObject * obj)
|
|
{
|
|
if (obj == NULL)
|
|
return g_strdup ("a NULL pointer");
|
|
|
|
if (obj->s.magic == CAMEL_OBJECT_MAGIC_VALUE) {
|
|
return g_strdup_printf ("an instance of `%s' at %p",
|
|
camel_type_to_name (obj->s.type),
|
|
obj);
|
|
} else if (obj->s.magic == CAMEL_OBJECT_FINALIZED_VALUE) {
|
|
return g_strdup_printf ("a finalized instance of `%s' at %p",
|
|
camel_type_to_name (obj->s.type),
|
|
obj);
|
|
} else if (obj->s.magic == CAMEL_OBJECT_CLASS_MAGIC_VALUE) {
|
|
return g_strdup_printf ("the classfuncs of `%s' at %p",
|
|
camel_type_to_name (obj->s.type),
|
|
obj);
|
|
} else if (obj->s.magic == CAMEL_OBJECT_CLASS_FINALIZED_VALUE) {
|
|
return
|
|
g_strdup_printf
|
|
("the finalized classfuncs of `%s' at %p",
|
|
camel_type_to_name (obj->s.type), obj);
|
|
}
|
|
|
|
return g_strdup ("not a CamelObject");
|
|
}
|
|
|
|
/* This is likely to be called in the class_init callback,
|
|
* and the type will likely be somewhat uninitialized.
|
|
* Is this a problem? We'll see....
|
|
*/
|
|
void
|
|
camel_object_class_declare_event (CamelObjectClass * class,
|
|
const gchar * name,
|
|
CamelObjectEventPrepFunc prep)
|
|
{
|
|
g_return_if_fail (CAMEL_IS_OBJECT_CLASS (class));
|
|
g_return_if_fail (name);
|
|
|
|
if (class->event_to_preplist == NULL)
|
|
class->event_to_preplist =
|
|
g_hash_table_new (g_str_hash, g_str_equal);
|
|
else if (g_hash_table_lookup (class->event_to_preplist, name) != NULL) {
|
|
g_warning
|
|
("camel_object_class_declare_event: event `%s' already declared for `%s'",
|
|
name, camel_type_to_name (class->s.type));
|
|
return;
|
|
}
|
|
|
|
/* AIEEEEEEEEEEEEEEEEEEEEEE
|
|
|
|
* I feel so naughty. Since it's valid to declare an event and not
|
|
* provide a hook, it should be valid to insert a NULL value into
|
|
* the table. However, then our lookup in trigger_event would be
|
|
* ambiguous, not telling us whether the event is undefined or whether
|
|
* it merely has no hook.
|
|
*
|
|
* So we create an 'NULL prep' value that != NULL... specifically, it
|
|
* equals the address of one of our static functions , because that
|
|
* can't possibly be your hook.
|
|
*
|
|
* Just don't forget to check for the 'evil value' and it'll work,
|
|
* I promise.
|
|
*/
|
|
|
|
if (prep == NULL)
|
|
prep = NULL_PREP_VALUE;
|
|
|
|
g_hash_table_insert (class->event_to_preplist, g_strdup (name), prep);
|
|
}
|
|
|
|
/* free hook data */
|
|
static void camel_object_free_hooks(CamelObject *o)
|
|
{
|
|
CamelHookPair *pair, *next;
|
|
|
|
if (o->hooks) {
|
|
|
|
g_assert(o->hooks->depth == 0);
|
|
g_assert((o->hooks->flags & CAMEL_HOOK_PAIR_REMOVED) == 0);
|
|
|
|
pair = o->hooks->list;
|
|
while (pair) {
|
|
next = pair->next;
|
|
g_free(pair);
|
|
pair = next;
|
|
}
|
|
e_mutex_destroy(o->hooks->lock);
|
|
g_free(o->hooks);
|
|
o->hooks = NULL;
|
|
}
|
|
}
|
|
|
|
/* return (allocate if required) the object's hook list, locking at the same time */
|
|
static CamelHookList *camel_object_get_hooks(CamelObject *o)
|
|
{
|
|
#ifdef ENABLE_THREADS
|
|
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
|
#endif
|
|
CamelHookList *hooks;
|
|
|
|
/* if we have it, we dont have to do any other locking,
|
|
otherwise use a global lock to setup the object's hook data */
|
|
#ifdef ENABLE_THREADS
|
|
if (o->hooks == NULL) {
|
|
pthread_mutex_lock(&lock);
|
|
#endif
|
|
if (o->hooks == NULL) {
|
|
hooks = g_malloc(sizeof(*o->hooks));
|
|
#ifdef ENABLE_THREADS
|
|
hooks->lock = e_mutex_new(E_MUTEX_REC);
|
|
#endif
|
|
hooks->flags = 0;
|
|
hooks->depth = 0;
|
|
hooks->list_length = 0;
|
|
hooks->list = NULL;
|
|
o->hooks = hooks;
|
|
}
|
|
#ifdef ENABLE_THREADS
|
|
pthread_mutex_unlock(&lock);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_THREADS
|
|
e_mutex_lock(o->hooks->lock);
|
|
#endif
|
|
return o->hooks;
|
|
}
|
|
|
|
/* unlock object hooks' list */
|
|
#ifdef ENABLE_THREADS
|
|
#define camel_object_unget_hooks(o) (e_mutex_unlock((CAMEL_OBJECT(o)->hooks->lock)))
|
|
#else
|
|
#define camel_object_unget_hooks(o)
|
|
#endif
|
|
|
|
void
|
|
camel_object_hook_event (CamelObject * obj, const char * name,
|
|
CamelObjectEventHookFunc func, void *data)
|
|
{
|
|
CamelHookPair *pair;
|
|
const char *prepname;
|
|
CamelObjectEventPrepFunc prep;
|
|
CamelHookList *hooks;
|
|
|
|
g_return_if_fail (CAMEL_IS_OBJECT (obj));
|
|
g_return_if_fail (name != NULL);
|
|
g_return_if_fail (func != NULL);
|
|
|
|
/* first, does this event exist? */
|
|
if (obj->classfuncs->event_to_preplist == NULL
|
|
|| !g_hash_table_lookup_extended(obj->classfuncs->event_to_preplist, name,
|
|
(void **)&prepname, (void **)&prep)) {
|
|
g_warning("camel_object_hook_event: trying to hook event `%s' in class `%s' with no defined events.",
|
|
name, camel_type_to_name (obj->s.type));
|
|
return;
|
|
}
|
|
|
|
/* setup hook pair */
|
|
pair = g_malloc(sizeof(*pair));
|
|
pair->name = prepname; /* effectively static! */
|
|
pair->func = func;
|
|
pair->data = data;
|
|
pair->flags = 0;
|
|
|
|
/* get the hook list object, locked, link in new event hook, unlock */
|
|
hooks = camel_object_get_hooks(obj);
|
|
pair->next = hooks->list;
|
|
hooks->list = pair;
|
|
hooks->list_length++;
|
|
camel_object_unget_hooks(obj);
|
|
}
|
|
|
|
void
|
|
camel_object_unhook_event (CamelObject * obj, const char * name,
|
|
CamelObjectEventHookFunc func, void *data)
|
|
{
|
|
char *prepname;
|
|
CamelObjectEventPrepFunc prep;
|
|
CamelHookList *hooks;
|
|
CamelHookPair *pair, *parent;
|
|
|
|
g_return_if_fail (CAMEL_IS_OBJECT (obj));
|
|
g_return_if_fail (name != NULL);
|
|
g_return_if_fail (func != NULL);
|
|
|
|
if (obj->hooks == NULL) {
|
|
g_warning("camel_object_unhook_event: trying to unhook `%s` from an instance of `%s' with no hooks",
|
|
name, camel_type_to_name(obj->s.type));
|
|
return;
|
|
}
|
|
|
|
/* get event name static pointer */
|
|
if (obj->classfuncs->event_to_preplist == NULL
|
|
|| !g_hash_table_lookup_extended(obj->classfuncs->event_to_preplist, name,
|
|
(void **)&prepname, (void **)&prep)) {
|
|
g_warning("camel_object_hook_event: trying to hook event `%s' in class `%s' with no defined events.",
|
|
name, camel_type_to_name (obj->s.type));
|
|
return;
|
|
}
|
|
|
|
/* scan hooks for this event, remove it, or flag it if we're busy */
|
|
hooks = camel_object_get_hooks(obj);
|
|
parent = (CamelHookPair *)&hooks->list;
|
|
pair = parent->next;
|
|
while (pair) {
|
|
if (pair->name == prepname
|
|
&& pair->func == func
|
|
&& pair->data == data
|
|
&& (pair->flags & CAMEL_HOOK_PAIR_REMOVED) == 0) {
|
|
if (hooks->depth > 0) {
|
|
pair->flags |= CAMEL_HOOK_PAIR_REMOVED;
|
|
hooks->flags |= CAMEL_HOOK_PAIR_REMOVED;
|
|
} else {
|
|
parent->next = pair->next;
|
|
g_free(pair);
|
|
hooks->list_length--;
|
|
}
|
|
camel_object_unget_hooks(obj);
|
|
return;
|
|
}
|
|
parent = pair;
|
|
pair = pair->next;
|
|
}
|
|
camel_object_unget_hooks(obj);
|
|
|
|
g_warning("camel_object_unhook_event: cannot find hook/data pair %p/%p in an instance of `%s' attached to `%s'",
|
|
func, data, camel_type_to_name (obj->s.type), name);
|
|
}
|
|
|
|
void
|
|
camel_object_trigger_event (CamelObject * obj, const char * name, void *event_data)
|
|
{
|
|
CamelObjectEventPrepFunc prep;
|
|
const char *prepname;
|
|
CamelHookList *hooks;
|
|
CamelHookPair *pair, **pairs, *parent;
|
|
int i, size;
|
|
|
|
g_return_if_fail (CAMEL_IS_OBJECT (obj));
|
|
g_return_if_fail (name);
|
|
|
|
/* get event name static pointer/prep func */
|
|
if (obj->classfuncs->event_to_preplist == NULL
|
|
|| !g_hash_table_lookup_extended(obj->classfuncs->event_to_preplist, name,
|
|
(void **)&prepname, (void **)&prep)) {
|
|
g_warning("camel_object_hook_event: trying to hook event `%s' in class `%s' with no defined events.",
|
|
name, camel_type_to_name (obj->s.type));
|
|
return;
|
|
}
|
|
|
|
/* try prep function, if false, then quit */
|
|
if (prep != NULL_PREP_VALUE && !prep(obj, event_data))
|
|
return;
|
|
|
|
/* also, no hooks, dont bother going further */
|
|
if (obj->hooks == NULL)
|
|
return;
|
|
|
|
/* lock the object for hook emission */
|
|
camel_object_ref(obj);
|
|
hooks = camel_object_get_hooks(obj);
|
|
|
|
if (hooks->list) {
|
|
/* first, copy the items in the list, and say we're in an event */
|
|
hooks->depth++;
|
|
pair = hooks->list;
|
|
size = 0;
|
|
pairs = alloca(sizeof(pairs[0]) * hooks->list_length);
|
|
while (pair) {
|
|
if (pair->name == prepname)
|
|
pairs[size++] = pair;
|
|
pair = pair->next;
|
|
}
|
|
|
|
/* now execute the events we have, if they haven't been removed during our calls */
|
|
for (i=0;i<size;i++) {
|
|
pair = pairs[i];
|
|
if ((pair->flags & CAMEL_HOOK_PAIR_REMOVED) == 0)
|
|
(pair->func) (obj, event_data, pair->data);
|
|
}
|
|
hooks->depth--;
|
|
|
|
/* and if we're out of any events, then clean up any pending removes */
|
|
if (hooks->depth == 0 && (hooks->flags & CAMEL_HOOK_PAIR_REMOVED)) {
|
|
parent = (CamelHookPair *)&hooks->list;
|
|
pair = parent->next;
|
|
while (pair) {
|
|
if (pair->flags & CAMEL_HOOK_PAIR_REMOVED) {
|
|
parent->next = pair->next;
|
|
g_free(pair);
|
|
hooks->list_length--;
|
|
} else {
|
|
parent = pair;
|
|
}
|
|
pair = parent->next;
|
|
}
|
|
hooks->flags &= ~CAMEL_HOOK_PAIR_REMOVED;
|
|
}
|
|
}
|
|
|
|
camel_object_unget_hooks(obj);
|
|
camel_object_unref(obj);
|
|
}
|
|
|
|
/* ** Static helpers ****************************************************** */
|
|
|
|
static gboolean
|
|
shared_is_of_type (CamelObjectShared * sh, CamelType ctype, gboolean is_obj)
|
|
{
|
|
CamelTypeInfo *type_info;
|
|
gchar *targtype;
|
|
|
|
if (is_obj)
|
|
targtype = "instance";
|
|
else
|
|
targtype = "classdata";
|
|
|
|
if (ctype == CAMEL_INVALID_TYPE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast to CAMEL_INVALID_TYPE");
|
|
return FALSE;
|
|
}
|
|
|
|
if (sh == NULL) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast NULL to %s of `%s'",
|
|
targtype, camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
|
|
if (sh->magic == CAMEL_OBJECT_FINALIZED_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast finalized instance "
|
|
"of `%s' into %s of `%s'",
|
|
camel_type_to_name (sh->type), targtype,
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
|
|
if (sh->magic == CAMEL_OBJECT_CLASS_FINALIZED_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast finalized classdata "
|
|
"of `%s' into %s of `%s'",
|
|
camel_type_to_name (sh->type), targtype,
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
|
|
if (is_obj) {
|
|
if (sh->magic == CAMEL_OBJECT_CLASS_MAGIC_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast classdata "
|
|
"of `%s' into instance of `%s'",
|
|
camel_type_to_name (sh->type),
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
|
|
if (sh->magic != CAMEL_OBJECT_MAGIC_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast junk data "
|
|
"into instance of `%s'",
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
if (sh->magic == CAMEL_OBJECT_MAGIC_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast instance "
|
|
"of `%s' into classdata of `%s'",
|
|
camel_type_to_name (sh->type),
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
|
|
if (sh->magic != CAMEL_OBJECT_CLASS_MAGIC_VALUE) {
|
|
g_warning
|
|
("shared_is_of_type: trying to cast junk data "
|
|
"into classdata of `%s'",
|
|
camel_type_to_name (ctype));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
camel_type_lock_up ();
|
|
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (sh->type));
|
|
|
|
if (type_info == NULL) {
|
|
g_warning ("shared_is_of_type: seemingly valid %s has "
|
|
"bad type %d.", targtype, sh->type);
|
|
camel_type_lock_down ();
|
|
return FALSE;
|
|
}
|
|
|
|
while (type_info) {
|
|
if (type_info->self == ctype) {
|
|
camel_type_lock_down ();
|
|
return TRUE;
|
|
}
|
|
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type_info->
|
|
parent));
|
|
}
|
|
|
|
/* this isn't an error, e.g. CAMEL_IS_FOLDER(folder), its upto the
|
|
caller to handle the false case */
|
|
/*g_warning
|
|
("shared_is_of_type: %s of `%s' (@%p) is not also %s of `%s'",
|
|
targtype, camel_type_to_name (sh->type), sh, targtype,
|
|
camel_type_to_name (ctype));*/
|
|
|
|
camel_type_lock_down ();
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
make_global_classfuncs (CamelTypeInfo * type_info)
|
|
{
|
|
CamelObjectClass *funcs;
|
|
GSList *parents;
|
|
GSList *head;
|
|
|
|
g_assert (type_info);
|
|
|
|
funcs = g_malloc0 (type_info->classfuncs_size);
|
|
funcs->s.type = type_info->self;
|
|
|
|
type_info->global_classfuncs = funcs;
|
|
|
|
parents = NULL;
|
|
while (type_info) {
|
|
parents = g_slist_prepend (parents, type_info);
|
|
type_info =
|
|
g_hash_table_lookup (ctype_to_typeinfo,
|
|
GINT_TO_POINTER (type_info->
|
|
parent));
|
|
}
|
|
|
|
head = parents;
|
|
|
|
for (; parents && parents->data; parents = parents->next) {
|
|
CamelTypeInfo *thisinfo;
|
|
|
|
thisinfo = parents->data;
|
|
if (thisinfo->class_init)
|
|
(thisinfo->class_init) (funcs);
|
|
}
|
|
|
|
g_slist_free (head);
|
|
}
|