diff options
Diffstat (limited to 'libedataserver')
41 files changed, 9746 insertions, 0 deletions
diff --git a/libedataserver/Makefile.am b/libedataserver/Makefile.am new file mode 100644 index 000000000..a118a6de8 --- /dev/null +++ b/libedataserver/Makefile.am @@ -0,0 +1,59 @@ +SUBDIRS = ename + +INCLUDES = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"e-data-server\" \ + -DG_DISABLE_DEPRECATED \ + $(DB3_CFLAGS) \ + $(E_DATA_SERVER_CFLAGS) + +# The marshallers +MARSHAL_GENERATED = e-data-server-marshal.c e-data-server-marshal.h +@EVO_MARSHAL_RULE@ + +lib_LTLIBRARIES = libedataserver.la + +libedataserver_la_SOURCES = \ + $(MARSHAL_GENERATED) \ + e-account-list.c \ + e-account.c \ + e-component-listener.c \ + e-dbhash.c \ + e-db3-utils.c \ + e-iterator.c \ + e-list.c \ + e-list-iterator.c \ + e-memory.c \ + e-msgport.c \ + e-sexp.c \ + e-uid.c \ + e-url.c \ + e-xml-hash-utils.c \ + md5-utils.c + +libedataserver_la_LIBADD = \ + $(DB3_LDADD) \ + $(E_DATA_SERVER_LIBS) + +libedataserver_la_LDFLAGS = \ + -version-info $(LIBEDATASERVER_CURRENT):$(LIBEDATASERVER_REVISION):$(LIBEDATASERVER_AGE) \ + -no-undefined + +libedataserverincludedir = $(includedir)/evolution-data-server-1.0/libedataserver + +libedataserverinclude_HEADERS = \ + e-account-list.h \ + e-account.h \ + e-component-listener.h \ + e-db3-utils.h \ + e-dbhash.h \ + e-iterator.h \ + e-list.h \ + e-list-iterator.h \ + e-memory.h \ + e-msgport.h \ + e-sexp.h \ + e-uid.h \ + e-url.h \ + e-xml-hash-utils.h \ + md5-utils.h
\ No newline at end of file diff --git a/libedataserver/e-account-list.c b/libedataserver/e-account-list.c new file mode 100644 index 000000000..b9fbb8de3 --- /dev/null +++ b/libedataserver/e-account-list.c @@ -0,0 +1,472 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 library; 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 "e-account-list.h" +#include "e-account.h" +#include "e-data-server-marshal.h" + +#include <string.h> +#include <gal/util/e-util.h> + +struct EAccountListPrivate { + GConfClient *gconf; + guint notify_id; +}; + +enum { + ACCOUNT_ADDED, + ACCOUNT_CHANGED, + ACCOUNT_REMOVED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +#define PARENT_TYPE E_TYPE_LIST +static EListClass *parent_class = NULL; + +static void dispose (GObject *); +static void finalize (GObject *); + +static void +class_init (GObjectClass *object_class) +{ + parent_class = g_type_class_ref (PARENT_TYPE); + + /* virtual method override */ + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* signals */ + signals[ACCOUNT_ADDED] = + g_signal_new ("account-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EAccountListClass, account_added), + NULL, NULL, + e_data_server_marshal_NONE__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_ACCOUNT); + signals[ACCOUNT_CHANGED] = + g_signal_new ("account-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EAccountListClass, account_changed), + NULL, NULL, + e_data_server_marshal_NONE__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_ACCOUNT); + signals[ACCOUNT_REMOVED] = + g_signal_new ("account-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EAccountListClass, account_removed), + NULL, NULL, + e_data_server_marshal_NONE__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_ACCOUNT); +} + +static void +init (GObject *object) +{ + EAccountList *account_list = E_ACCOUNT_LIST (object); + + account_list->priv = g_new0 (EAccountListPrivate, 1); +} + +static void +dispose (GObject *object) +{ + EAccountList *account_list = E_ACCOUNT_LIST (object); + + if (account_list->priv->gconf) { + if (account_list->priv->notify_id) { + gconf_client_notify_remove (account_list->priv->gconf, + account_list->priv->notify_id); + } + g_object_unref (account_list->priv->gconf); + account_list->priv->gconf = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + EAccountList *account_list = E_ACCOUNT_LIST (object); + + g_free (account_list->priv); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +E_MAKE_TYPE (e_account_list, "EAccountList", EAccountList, class_init, init, PARENT_TYPE) + + +static void +gconf_accounts_changed (GConfClient *client, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + EAccountList *account_list = user_data; + GSList *list, *l, *new_accounts = NULL; + EAccount *account; + EList *old_accounts; + EIterator *iter; + char *uid; + + old_accounts = e_list_duplicate (E_LIST (account_list)); + + list = gconf_client_get_list (client, "/apps/evolution/mail/accounts", + GCONF_VALUE_STRING, NULL); + for (l = list; l; l = l->next) { + uid = e_account_uid_from_xml (l->data); + if (!uid) + continue; + + /* See if this is an existing account */ + for (iter = e_list_get_iterator (old_accounts); + e_iterator_is_valid (iter); + e_iterator_next (iter)) { + account = (EAccount *)e_iterator_get (iter); + if (!strcmp (account->uid, uid)) { + /* The account still exists, so remove + * it from "old_accounts" and update it. + */ + e_iterator_delete (iter); + if (e_account_set_from_xml (account, l->data)) + g_signal_emit (account_list, signals[ACCOUNT_CHANGED], 0, account); + goto next; + } + } + + /* Must be a new account */ + account = e_account_new_from_xml (l->data); + e_list_append (E_LIST (account_list), account); + new_accounts = g_slist_prepend (new_accounts, account); + + next: + g_free (uid); + g_object_unref (iter); + } + + /* Now emit signals for each added account. (We do this after + * adding all of them because otherwise if the signal handler + * calls e_account_list_get_default_account() it will end up + * causing the first account in the list to become the + * default.) + */ + for (l = new_accounts; l; l = l->next) { + account = l->data; + g_signal_emit (account_list, signals[ACCOUNT_ADDED], 0, account); + g_object_unref (account); + } + g_slist_free (new_accounts); + + /* Anything left in old_accounts must have been deleted */ + for (iter = e_list_get_iterator (old_accounts); + e_iterator_is_valid (iter); + e_iterator_next (iter)) { + account = (EAccount *)e_iterator_get (iter); + e_list_remove (E_LIST (account_list), account); + g_signal_emit (account_list, signals[ACCOUNT_REMOVED], 0, account); + } + g_object_unref (iter); + g_object_unref (old_accounts); +} + +static void * +copy_func (const void *data, void *closure) +{ + GObject *object = (GObject *)data; + + g_object_ref (object); + return object; +} + +static void +free_func (void *data, void *closure) +{ + g_object_unref (data); +} + +/** + * e_account_list_new: + * @gconf: a #GConfClient + * + * Reads the list of accounts from @gconf and listens for changes. + * Will emit %account_added, %account_changed, and %account_removed + * signals according to notifications from GConf. + * + * You can modify the list using e_list_append(), e_list_remove(), and + * e_iterator_delete(). After adding, removing, or changing accounts, + * you must call e_account_list_save() to push the changes back to + * GConf. + * + * Return value: the list of accounts + **/ +EAccountList * +e_account_list_new (GConfClient *gconf) +{ + EAccountList *account_list; + + g_return_val_if_fail (GCONF_IS_CLIENT (gconf), NULL); + + account_list = g_object_new (E_TYPE_ACCOUNT_LIST, NULL); + e_account_list_construct (account_list, gconf); + + return account_list; +} + +void +e_account_list_construct (EAccountList *account_list, GConfClient *gconf) +{ + g_return_if_fail (GCONF_IS_CLIENT (gconf)); + + e_list_construct (E_LIST (account_list), copy_func, free_func, NULL); + account_list->priv->gconf = gconf; + g_object_ref (gconf); + + gconf_client_add_dir (account_list->priv->gconf, + "/apps/evolution/mail/accounts", + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + account_list->priv->notify_id = + gconf_client_notify_add (account_list->priv->gconf, + "/apps/evolution/mail/accounts", + gconf_accounts_changed, account_list, + NULL, NULL); + + gconf_accounts_changed (account_list->priv->gconf, + account_list->priv->notify_id, + NULL, account_list); +} + +/** + * e_account_list_save: + * @account_list: an #EAccountList + * + * Saves @account_list to GConf. Signals will be emitted for changes. + **/ +void +e_account_list_save (EAccountList *account_list) +{ + GSList *list = NULL; + EAccount *account; + EIterator *iter; + char *xmlbuf; + + for (iter = e_list_get_iterator (E_LIST (account_list)); + e_iterator_is_valid (iter); + e_iterator_next (iter)) { + account = (EAccount *)e_iterator_get (iter); + + xmlbuf = e_account_to_xml (account); + if (xmlbuf) + list = g_slist_append (list, xmlbuf); + } + g_object_unref (iter); + + gconf_client_set_list (account_list->priv->gconf, + "/apps/evolution/mail/accounts", + GCONF_VALUE_STRING, list, NULL); + + while (list) { + g_free (list->data); + list = g_slist_remove (list, list->data); + } + + gconf_client_suggest_sync (account_list->priv->gconf, NULL); +} + +/** + * e_account_list_add: + * @accounts: + * @account: + * + * Add an account to the account list. Will emit the account-changed + * event. + **/ +void +e_account_list_add(EAccountList *accounts, EAccount *account) +{ + /* FIXME: should we check for duplicate accounts? */ + + e_list_append ((EList *)accounts, account); + g_signal_emit(accounts, signals[ACCOUNT_ADDED], 0, account); +} + +/** + * e_account_list_change: + * @accounts: + * @account: + * + * Signal that the details of an account have changed. + **/ +void +e_account_list_change(EAccountList *accounts, EAccount *account) +{ + /* maybe the account should do this itself ... */ + g_signal_emit(accounts, signals[ACCOUNT_CHANGED], 0, account); +} + +/** + * e_account_list_remove: + * @accounts: + * @account: + * + * Remove an account from the account list, and emit the + * account-removed signal. If the account was the default account, + * then reset the default to the first account. + **/ +void +e_account_list_remove(EAccountList *accounts, EAccount *account) +{ + if (account == e_account_list_get_default(accounts)) + gconf_client_unset (accounts->priv->gconf, "/apps/evolution/mail/default_account", NULL); + + /* not sure if need to ref but no harm */ + g_object_ref (account); + e_list_remove ((EList *) accounts, account); + g_signal_emit(accounts, signals[ACCOUNT_REMOVED], 0, account); + g_object_unref (account); +} + +/** + * e_account_list_get_default: + * @accounts: + * + * Get the default account. If no default is specified, or the default + * has become stale, then the first account is made the default. + * + * Return value: The account or NULL if no accounts are defined. + **/ +const EAccount * +e_account_list_get_default(EAccountList *accounts) +{ + char *uid; + EIterator *it; + const EAccount *account = NULL; + + uid = gconf_client_get_string (accounts->priv->gconf, "/apps/evolution/mail/default_account", NULL); + it = e_list_get_iterator ((EList *)accounts); + + if (uid) { + for (;e_iterator_is_valid (it);e_iterator_next (it)) { + account = (const EAccount *)e_iterator_get (it); + + if (!strcmp(uid, account->uid)) + break; + account = NULL; + } + e_iterator_reset(it); + } + + /* no uid or uid not found, @it will be at the first account */ + if (account == NULL && e_iterator_is_valid(it)) { + account = (const EAccount *) e_iterator_get (it); + gconf_client_set_string (accounts->priv->gconf, "/apps/evolution/mail/default_account", account->uid, NULL); + } + + g_object_unref(it); + g_free(uid); + + return account; +} + +/** + * e_account_list_set_default: + * @accounts: + * @account: + * + * Set the account @account to be the default account. + **/ +void +e_account_list_set_default(EAccountList *accounts, EAccount *account) +{ + gconf_client_set_string (accounts->priv->gconf, "/apps/evolution/mail/default_account", account->uid, NULL); +} + +/** + * e_account_list_find: + * @accounts: + * @type: Type of search. + * @key: Search key. + * + * Perform a search of the account list on a single key. + * + * @type must be set from one of the following search types: + * E_ACCOUNT_FIND_NAME - Find an account by account name. + * E_ACCOUNT_FIND_ID_NAME - Find an account by the owner's identity name. + * E_ACCOUNT_FIND_ID_ADDRESS - Find an account by the owner's identity address. + * + * Return value: The account or NULL if it doesn't exist. + **/ +const EAccount * +e_account_list_find(EAccountList *accounts, e_account_find_t type, const char *key) +{ + char *val; + EIterator *it; + const EAccount *account = NULL; + + /* this could use a callback for more flexibility ... + ... but this makes the common cases easier */ + + if (!key) + return NULL; + + for (it = e_list_get_iterator ((EList *)accounts); + e_iterator_is_valid (it); + e_iterator_next (it)) { + int found = 0; + + account = (const EAccount *)e_iterator_get (it); + + val = NULL; + switch(type) { + case E_ACCOUNT_FIND_NAME: + found = strcmp(account->name, key) == 0; + break; + case E_ACCOUNT_FIND_UID: + found = strcmp(account->uid, key) == 0; + break; + case E_ACCOUNT_FIND_ID_NAME: + if (account->id) + found = strcmp(account->id->name, key) == 0; + break; + case E_ACCOUNT_FIND_ID_ADDRESS: + if (account->id) + found = g_ascii_strcasecmp(account->id->address, key) == 0; + break; + } + + if (found) + break; + + account = NULL; + } + g_object_unref(it); + + return account; +} + diff --git a/libedataserver/e-account-list.h b/libedataserver/e-account-list.h new file mode 100644 index 000000000..b47efb42a --- /dev/null +++ b/libedataserver/e-account-list.h @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __E_ACCOUNT_LIST__ +#define __E_ACCOUNT_LIST__ + +#include "e-list.h" +#include "e-account.h" +#include <gconf/gconf-client.h> + +#define E_TYPE_ACCOUNT_LIST (e_account_list_get_type ()) +#define E_ACCOUNT_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_ACCOUNT_LIST, EAccountList)) +#define E_ACCOUNT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_ACCOUNT_LIST, EAccountListClass)) +#define E_IS_ACCOUNT_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_ACCOUNT_LIST)) +#define E_IS_ACCOUNT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_ACCOUNT_LIST)) + +typedef struct EAccountListPrivate EAccountListPrivate; + +/* search options for the find command */ +typedef enum _e_account_find_t { + E_ACCOUNT_FIND_NAME, + E_ACCOUNT_FIND_UID, + E_ACCOUNT_FIND_ID_NAME, + E_ACCOUNT_FIND_ID_ADDRESS, +} e_account_find_t; + +typedef struct { + EList parent_object; + + EAccountListPrivate *priv; +} EAccountList; + +typedef struct { + EListClass parent_class; + + /* signals */ + void (*account_added) (EAccountList *, EAccount *); + void (*account_changed) (EAccountList *, EAccount *); + void (*account_removed) (EAccountList *, EAccount *); +} EAccountListClass; + + +GType e_account_list_get_type (void); + +EAccountList *e_account_list_new (GConfClient *gconf); +void e_account_list_construct (EAccountList *account_list, + GConfClient *gconf); + +void e_account_list_save (EAccountList *account_list); + +void e_account_list_add (EAccountList *, EAccount *); +void e_account_list_change (EAccountList *, EAccount *); +void e_account_list_remove (EAccountList *, EAccount *); + +const EAccount *e_account_list_get_default(EAccountList *); +void e_account_list_set_default(EAccountList *, EAccount *); +const EAccount *e_account_list_find (EAccountList *, e_account_find_t type, const char *key); + +#endif /* __E_ACCOUNT_LIST__ */ diff --git a/libedataserver/e-account.c b/libedataserver/e-account.c new file mode 100644 index 000000000..19bd0eadd --- /dev/null +++ b/libedataserver/e-account.c @@ -0,0 +1,561 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 library; 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 "e-account.h" + +#include "e-uid.h" + +#include <string.h> + +#include <gal/util/e-util.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/xmlmemory.h> + +#define PARENT_TYPE G_TYPE_OBJECT +static GObjectClass *parent_class = NULL; + +static void finalize (GObject *); + +static void +class_init (GObjectClass *object_class) +{ + parent_class = g_type_class_ref (PARENT_TYPE); + + /* virtual method override */ + object_class->finalize = finalize; +} + +static void +init (EAccount *account) +{ + account->id = g_new0 (EAccountIdentity, 1); + account->source = g_new0 (EAccountService, 1); + account->transport = g_new0 (EAccountService, 1); +} + +static void +identity_destroy (EAccountIdentity *id) +{ + if (!id) + return; + + g_free (id->name); + g_free (id->address); + g_free (id->reply_to); + g_free (id->organization); + + g_free (id); +} + +static void +service_destroy (EAccountService *service) +{ + if (!service) + return; + + g_free (service->url); + + g_free (service); +} + +static void +finalize (GObject *object) +{ + EAccount *account = E_ACCOUNT (object); + + g_free (account->name); + g_free (account->uid); + + identity_destroy (account->id); + service_destroy (account->source); + service_destroy (account->transport); + + g_free (account->drafts_folder_uri); + g_free (account->sent_folder_uri); + + g_free (account->cc_addrs); + g_free (account->bcc_addrs); + + g_free (account->pgp_key); + g_free (account->smime_sign_key); + g_free (account->smime_encrypt_key); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +E_MAKE_TYPE (e_account, "EAccount", EAccount, class_init, init, PARENT_TYPE) + + +/** + * e_account_new: + * + * Return value: a blank new account which can be filled in and + * added to an #EAccountList. + **/ +EAccount * +e_account_new (void) +{ + EAccount *account; + + account = g_object_new (E_TYPE_ACCOUNT, NULL); + account->uid = e_uid_new (); + + return account; +} + +/** + * e_account_new_from_xml: + * @xml: an XML account description + * + * Return value: a new #EAccount based on the data in @xml, or %NULL + * if @xml could not be parsed as valid account data. + **/ +EAccount * +e_account_new_from_xml (const char *xml) +{ + EAccount *account; + + account = g_object_new (E_TYPE_ACCOUNT, NULL); + if (!e_account_set_from_xml (account, xml)) { + g_object_unref (account); + return NULL; + } + + return account; +} + + +static gboolean +xml_set_bool (xmlNodePtr node, const char *name, gboolean *val) +{ + gboolean bool; + char *buf; + + if ((buf = xmlGetProp (node, name))) { + bool = (!strcmp (buf, "true") || !strcmp (buf, "yes")); + xmlFree (buf); + + if (bool != *val) { + *val = bool; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +xml_set_int (xmlNodePtr node, const char *name, int *val) +{ + int number; + char *buf; + + if ((buf = xmlGetProp (node, name))) { + number = strtol (buf, NULL, 10); + xmlFree (buf); + + if (number != *val) { + *val = number; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +xml_set_prop (xmlNodePtr node, const char *name, char **val) +{ + char *buf, *new_val; + + buf = xmlGetProp (node, name); + new_val = g_strdup (buf); + xmlFree (buf); + + /* We can use strcmp here whether the value is UTF8 or + * not, since we only care if the bytes changed. + */ + if (!*val || strcmp (*val, new_val)) { + g_free (*val); + *val = new_val; + return TRUE; + } else { + g_free (new_val); + return FALSE; + } +} + +static gboolean +xml_set_content (xmlNodePtr node, char **val) +{ + char *buf, *new_val; + + buf = xmlNodeGetContent (node); + new_val = g_strdup (buf); + xmlFree (buf); + + /* We can use strcmp here whether the value is UTF8 or + * not, since we only care if the bytes changed. + */ + if (!*val || strcmp (*val, new_val)) { + g_free (*val); + *val = new_val; + return TRUE; + } else { + g_free (new_val); + return FALSE; + } +} + +static gboolean +xml_set_identity (xmlNodePtr node, EAccountIdentity *id) +{ + gboolean changed = FALSE; + + for (node = node->children; node; node = node->next) { + if (!strcmp (node->name, "name")) + changed |= xml_set_content (node, &id->name); + else if (!strcmp (node->name, "addr-spec")) + changed |= xml_set_content (node, &id->address); + else if (!strcmp (node->name, "reply-to")) + changed |= xml_set_content (node, &id->reply_to); + else if (!strcmp (node->name, "organization")) + changed |= xml_set_content (node, &id->organization); + else if (!strcmp (node->name, "signature")) { + changed |= xml_set_bool (node, "auto", &id->auto_signature); + changed |= xml_set_int (node, "default", &id->def_signature); + } + } + + return changed; +} + +static gboolean +xml_set_service (xmlNodePtr node, EAccountService *service) +{ + gboolean changed = FALSE; + + changed |= xml_set_bool (node, "save-passwd", &service->save_passwd); + changed |= xml_set_bool (node, "keep-on-server", &service->keep_on_server); + + changed |= xml_set_bool (node, "auto-check", &service->auto_check); + changed |= xml_set_int (node, "auto-check-timeout", &service->auto_check_time); + if (service->auto_check && service->auto_check_time <= 0) { + service->auto_check = FALSE; + service->auto_check_time = 0; + } + + for (node = node->children; node; node = node->next) { + if (!strcmp (node->name, "url")) { + changed |= xml_set_content (node, &service->url); + break; + } + } + + return changed; +} + +/** + * e_account_set_from_xml: + * @account: an #EAccount + * @xml: an XML account description. + * + * Changes @account to match @xml. + * + * Return value: %TRUE if @account was changed, %FALSE if @account + * already matched @xml or @xml could not be parsed + **/ +gboolean +e_account_set_from_xml (EAccount *account, const char *xml) +{ + xmlNodePtr node, cur; + xmlDocPtr doc; + gboolean changed = FALSE; + + if (!(doc = xmlParseDoc ((char *)xml))) + return FALSE; + + node = doc->children; + if (strcmp (node->name, "account") != 0) { + xmlFreeDoc (doc); + return FALSE; + } + + if (!account->uid) + xml_set_prop (node, "uid", &account->uid); + + changed |= xml_set_prop (node, "name", &account->name); + changed |= xml_set_bool (node, "enabled", &account->enabled); + + for (node = node->children; node; node = node->next) { + if (!strcmp (node->name, "identity")) { + changed |= xml_set_identity (node, account->id); + } else if (!strcmp (node->name, "source")) { + changed |= xml_set_service (node, account->source); + } else if (!strcmp (node->name, "transport")) { + changed |= xml_set_service (node, account->transport); + } else if (!strcmp (node->name, "drafts-folder")) { + changed |= xml_set_content (node, &account->drafts_folder_uri); + } else if (!strcmp (node->name, "sent-folder")) { + changed |= xml_set_content (node, &account->sent_folder_uri); + } else if (!strcmp (node->name, "auto-cc")) { + changed |= xml_set_bool (node, "always", &account->always_cc); + changed |= xml_set_content (node, &account->cc_addrs); + } else if (!strcmp (node->name, "auto-bcc")) { + changed |= xml_set_bool (node, "always", &account->always_bcc); + changed |= xml_set_content (node, &account->bcc_addrs); + } else if (!strcmp (node->name, "pgp")) { + changed |= xml_set_bool (node, "encrypt-to-self", &account->pgp_encrypt_to_self); + changed |= xml_set_bool (node, "always-trust", &account->pgp_always_trust); + changed |= xml_set_bool (node, "always-sign", &account->pgp_always_sign); + changed |= xml_set_bool (node, "no-imip-sign", &account->pgp_no_imip_sign); + + if (node->children) { + for (cur = node->children; cur; cur = cur->next) { + if (!strcmp (cur->name, "key-id")) { + changed |= xml_set_content (cur, &account->pgp_key); + break; + } + } + } + } else if (!strcmp (node->name, "smime")) { + changed |= xml_set_bool (node, "sign-default", &account->smime_sign_default); + changed |= xml_set_bool (node, "encrypt-to-self", &account->smime_encrypt_to_self); + changed |= xml_set_bool (node, "encrypt-default", &account->smime_encrypt_default); + + if (node->children) { + for (cur = node->children; cur; cur = cur->next) { + if (!strcmp (cur->name, "sign-key-id")) { + changed |= xml_set_content (cur, &account->smime_sign_key); + } else if (!strcmp (cur->name, "encrypt-key-id")) { + changed |= xml_set_content (cur, &account->smime_encrypt_key); + break; + } + } + } + } + } + + xmlFreeDoc (doc); + + return changed; +} + + +/** + * e_account_import: + * @dest: destination account object + * @src: source account object + * + * Import the settings from @src to @dest. + **/ +void +e_account_import (EAccount *dest, EAccount *src) +{ + g_free (dest->name); + dest->name = g_strdup (src->name); + + dest->enabled = src->enabled; + + g_free (dest->id->name); + dest->id->name = g_strdup (src->id->name); + g_free (dest->id->address); + dest->id->address = g_strdup (src->id->address); + g_free (dest->id->reply_to); + dest->id->reply_to = g_strdup (src->id->reply_to); + g_free (dest->id->organization); + dest->id->organization = g_strdup (src->id->organization); + dest->id->def_signature = src->id->def_signature; + dest->id->auto_signature = src->id->auto_signature; + + g_free (dest->source->url); + dest->source->url = g_strdup (src->source->url); + dest->source->keep_on_server = src->source->keep_on_server; + dest->source->auto_check = src->source->auto_check; + dest->source->auto_check_time = src->source->auto_check_time; + dest->source->save_passwd = src->source->save_passwd; + + g_free (dest->transport->url); + dest->transport->url = g_strdup (src->transport->url); + dest->transport->save_passwd = src->transport->save_passwd; + + g_free (dest->drafts_folder_uri); + dest->drafts_folder_uri = g_strdup (src->drafts_folder_uri); + + g_free (dest->sent_folder_uri); + dest->sent_folder_uri = g_strdup (src->sent_folder_uri); + + dest->always_cc = src->always_cc; + g_free (dest->cc_addrs); + dest->cc_addrs = g_strdup (src->cc_addrs); + + dest->always_bcc = src->always_bcc; + g_free (dest->bcc_addrs); + dest->bcc_addrs = g_strdup (src->bcc_addrs); + + g_free (dest->pgp_key); + dest->pgp_key = g_strdup (src->pgp_key); + dest->pgp_encrypt_to_self = src->pgp_encrypt_to_self; + dest->pgp_always_sign = src->pgp_always_sign; + dest->pgp_no_imip_sign = src->pgp_no_imip_sign; + dest->pgp_always_trust = src->pgp_always_trust; + + dest->smime_sign_default = src->smime_sign_default; + g_free (dest->smime_sign_key); + dest->smime_sign_key = g_strdup (src->smime_sign_key); + + dest->smime_encrypt_default = src->smime_encrypt_default; + dest->smime_encrypt_to_self = src->smime_encrypt_to_self; + g_free (dest->smime_encrypt_key); + dest->smime_encrypt_key = g_strdup (src->smime_encrypt_key); +} + + +/** + * e_account_to_xml: + * @account: an #EAccount + * + * Return value: an XML representation of @account, which the caller + * must free. + **/ +char * +e_account_to_xml (EAccount *account) +{ + xmlNodePtr root, node, id, src, xport; + char *tmp, buf[20]; + xmlChar *xmlbuf; + xmlDocPtr doc; + int n; + + doc = xmlNewDoc ("1.0"); + + root = xmlNewDocNode (doc, NULL, "account", NULL); + xmlDocSetRootElement (doc, root); + + xmlSetProp (root, "name", account->name); + xmlSetProp (root, "uid", account->uid); + xmlSetProp (root, "enabled", account->enabled ? "true" : "false"); + + id = xmlNewChild (root, NULL, "identity", NULL); + if (account->id->name) + xmlNewTextChild (id, NULL, "name", account->id->name); + if (account->id->address) + xmlNewTextChild (id, NULL, "addr-spec", account->id->address); + if (account->id->reply_to) + xmlNewTextChild (id, NULL, "reply-to", account->id->reply_to); + if (account->id->organization) + xmlNewTextChild (id, NULL, "organization", account->id->organization); + + node = xmlNewChild (id, NULL, "signature",NULL); + xmlSetProp (node, "auto", account->id->auto_signature ? "true" : "false"); + sprintf (buf, "%d", account->id->def_signature); + xmlSetProp (node, "default", buf); + + src = xmlNewChild (root, NULL, "source", NULL); + xmlSetProp (src, "save-passwd", account->source->save_passwd ? "true" : "false"); + xmlSetProp (src, "keep-on-server", account->source->keep_on_server ? "true" : "false"); + xmlSetProp (src, "auto-check", account->source->auto_check ? "true" : "false"); + sprintf (buf, "%d", account->source->auto_check_time); + xmlSetProp (src, "auto-check-timeout", buf); + if (account->source->url) + xmlNewTextChild (src, NULL, "url", account->source->url); + + xport = xmlNewChild (root, NULL, "transport", NULL); + xmlSetProp (xport, "save-passwd", account->transport->save_passwd ? "true" : "false"); + if (account->transport->url) + xmlNewTextChild (xport, NULL, "url", account->transport->url); + + xmlNewTextChild (root, NULL, "drafts-folder", account->drafts_folder_uri); + xmlNewTextChild (root, NULL, "sent-folder", account->sent_folder_uri); + + node = xmlNewChild (root, NULL, "auto-cc", NULL); + xmlSetProp (node, "always", account->always_cc ? "true" : "false"); + if (account->cc_addrs) + xmlNewTextChild (node, NULL, "recipients", account->cc_addrs); + + node = xmlNewChild (root, NULL, "auto-bcc", NULL); + xmlSetProp (node, "always", account->always_bcc ? "true" : "false"); + if (account->bcc_addrs) + xmlNewTextChild (node, NULL, "recipients", account->bcc_addrs); + + node = xmlNewChild (root, NULL, "pgp", NULL); + xmlSetProp (node, "encrypt-to-self", account->pgp_encrypt_to_self ? "true" : "false"); + xmlSetProp (node, "always-trust", account->pgp_always_trust ? "true" : "false"); + xmlSetProp (node, "always-sign", account->pgp_always_sign ? "true" : "false"); + xmlSetProp (node, "no-imip-sign", account->pgp_no_imip_sign ? "true" : "false"); + if (account->pgp_key) + xmlNewTextChild (node, NULL, "key-id", account->pgp_key); + + node = xmlNewChild (root, NULL, "smime", NULL); + xmlSetProp (node, "sign-default", account->smime_sign_default ? "true" : "false"); + xmlSetProp (node, "encrypt-default", account->smime_encrypt_default ? "true" : "false"); + xmlSetProp (node, "encrypt-to-self", account->smime_encrypt_to_self ? "true" : "false"); + if (account->smime_sign_key) + xmlNewTextChild (node, NULL, "sign-key-id", account->smime_sign_key); + if (account->smime_encrypt_key) + xmlNewTextChild (node, NULL, "encrypt-key-id", account->smime_encrypt_key); + + xmlDocDumpMemory (doc, &xmlbuf, &n); + xmlFreeDoc (doc); + + /* remap to glib memory */ + tmp = g_malloc (n + 1); + memcpy (tmp, xmlbuf, n); + tmp[n] = '\0'; + xmlFree (xmlbuf); + + return tmp; +} + + +/** + * e_account_uid_from_xml: + * @xml: an XML account description + * + * Return value: the permanent UID of the account described by @xml + * (or %NULL if @xml could not be parsed or did not contain a uid). + * The caller must free this string. + **/ +char * +e_account_uid_from_xml (const char *xml) +{ + xmlNodePtr node; + xmlDocPtr doc; + char *uid = NULL; + + if (!(doc = xmlParseDoc ((char *)xml))) + return NULL; + + node = doc->children; + if (strcmp (node->name, "account") != 0) { + xmlFreeDoc (doc); + return NULL; + } + + xml_set_prop (node, "uid", &uid); + xmlFreeDoc (doc); + + return uid; +} diff --git a/libedataserver/e-account.h b/libedataserver/e-account.h new file mode 100644 index 000000000..e50c77399 --- /dev/null +++ b/libedataserver/e-account.h @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003 Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __E_ACCOUNT__ +#define __E_ACCOUNT__ + +#include <glib-object.h> + +#define E_TYPE_ACCOUNT (e_account_get_type ()) +#define E_ACCOUNT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_ACCOUNT, EAccount)) +#define E_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_ACCOUNT, EAccountClass)) +#define E_IS_ACCOUNT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_ACCOUNT)) +#define E_IS_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_ACCOUNT)) + +typedef struct { + char *name; + char *address; + char *reply_to; + char *organization; + + int def_signature; + gboolean auto_signature; +} EAccountIdentity; + +typedef struct { + char *url; + gboolean keep_on_server; + gboolean auto_check; + int auto_check_time; + gboolean save_passwd; +} EAccountService; + + +typedef struct { + GObject parent_object; + + char *name; + char *uid; + + gboolean enabled; + + EAccountIdentity *id; + EAccountService *source; + EAccountService *transport; + + char *drafts_folder_uri, *sent_folder_uri; + + gboolean always_cc; + char *cc_addrs; + gboolean always_bcc; + char *bcc_addrs; + + char *pgp_key; + gboolean pgp_encrypt_to_self; + gboolean pgp_always_sign; + gboolean pgp_no_imip_sign; + gboolean pgp_always_trust; + + char *smime_sign_key; + char *smime_encrypt_key; + gboolean smime_sign_default; + gboolean smime_encrypt_to_self; + gboolean smime_encrypt_default; +} EAccount; + +typedef struct { + GObjectClass parent_class; + +} EAccountClass; + + +GType e_account_get_type (void); + +EAccount *e_account_new (void); + +EAccount *e_account_new_from_xml (const char *xml); + +gboolean e_account_set_from_xml (EAccount *account, + const char *xml); + +void e_account_import (EAccount *dest, + EAccount *src); + +char *e_account_to_xml (EAccount *account); + + +char *e_account_uid_from_xml (const char *xml); + + +#endif /* __E_ACCOUNT__ */ diff --git a/libedataserver/e-component-listener.c b/libedataserver/e-component-listener.c new file mode 100644 index 000000000..1e7027f33 --- /dev/null +++ b/libedataserver/e-component-listener.c @@ -0,0 +1,171 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Component listener. + * + * Author: + * Rodrigo Moya <rodrigo@ximian.com> + * + * Copyright 2002, Ximian, Inc. + */ + +#include <bonobo/bonobo-exception.h> +#include <bonobo/bonobo-object.h> +#include "e-component-listener.h" +#include <libgnome/gnome-i18n.h> + +#define PARENT_TYPE GTK_TYPE_OBJECT + +struct _EComponentListenerPrivate { + Bonobo_Unknown component; +}; + +static void e_component_listener_class_init (EComponentListenerClass *klass); +static void e_component_listener_init (EComponentListener *cl, EComponentListenerClass *klass); +static void e_component_listener_finalize (GObject *object); + +static GObjectClass *parent_class = NULL; +static GList *watched_connections = NULL; + +enum { + COMPONENT_DIED, + LAST_SIGNAL +}; + +static guint comp_listener_signals[LAST_SIGNAL]; + +static void +connection_listen_cb (gpointer object, gpointer user_data) +{ + GList *l, *next = NULL; + EComponentListener *cl; + + for (l = watched_connections; l != NULL; l = next) { + next = l->next; + cl = l->data; + + switch (ORBit_small_get_connection_status (cl->priv->component)) { + case ORBIT_CONNECTION_DISCONNECTED : + watched_connections = g_list_delete_link (watched_connections, l); + + g_object_ref (cl); + g_signal_emit (cl, comp_listener_signals[COMPONENT_DIED], 0); + cl->priv->component = CORBA_OBJECT_NIL; + g_object_unref (cl); + break; + default : + break; + } + } +} + +static void +e_component_listener_class_init (EComponentListenerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = e_component_listener_finalize; + klass->component_died = NULL; + + comp_listener_signals[COMPONENT_DIED] = + g_signal_new ("component_died", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EComponentListenerClass, component_died), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +e_component_listener_init (EComponentListener *cl, EComponentListenerClass *klass) +{ + /* allocate internal structure */ + cl->priv = g_new (EComponentListenerPrivate, 1); + cl->priv->component = CORBA_OBJECT_NIL; +} + +static void +e_component_listener_finalize (GObject *object) +{ + EComponentListener *cl = (EComponentListener *) object; + + g_return_if_fail (E_IS_COMPONENT_LISTENER (cl)); + + watched_connections = g_list_remove (watched_connections, cl); + + if (cl->priv->component != CORBA_OBJECT_NIL) + cl->priv->component = CORBA_OBJECT_NIL; + + /* free memory */ + g_free (cl->priv); + cl->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +GType +e_component_listener_get_type (void) +{ + static GType type = 0; + + if (!type) { + static GTypeInfo info = { + sizeof (EComponentListenerClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) e_component_listener_class_init, + NULL, NULL, + sizeof (EComponentListener), + 0, + (GInstanceInitFunc) e_component_listener_init + }; + type = g_type_register_static (G_TYPE_OBJECT, "EComponentListener", &info, 0); + } + + return type; +} + +/** + * e_component_listener_new + * @comp: Component to listen for. + * + * Create a new #EComponentListener object, which allows to listen + * for a given component and get notified when that component dies. + * + * Returns: a component listener object. + */ +EComponentListener * +e_component_listener_new (Bonobo_Unknown comp) +{ + EComponentListener *cl; + + g_return_val_if_fail (comp != NULL, NULL); + + cl = g_object_new (E_COMPONENT_LISTENER_TYPE, NULL); + cl->priv->component = comp; + + /* watch the connection */ + ORBit_small_listen_for_broken (comp, G_CALLBACK (connection_listen_cb), cl); + watched_connections = g_list_prepend (watched_connections, cl); + + return cl; +} + +Bonobo_Unknown +e_component_listener_get_component (EComponentListener *cl) +{ + g_return_val_if_fail (E_IS_COMPONENT_LISTENER (cl), CORBA_OBJECT_NIL); + return cl->priv->component; +} + +void +e_component_listener_set_component (EComponentListener *cl, Bonobo_Unknown comp) +{ + g_return_if_fail (E_IS_COMPONENT_LISTENER (cl)); + + cl->priv->component = comp; + ORBit_small_listen_for_broken (comp, G_CALLBACK (connection_listen_cb), cl); +} diff --git a/libedataserver/e-component-listener.h b/libedataserver/e-component-listener.h new file mode 100644 index 000000000..3f5694ecd --- /dev/null +++ b/libedataserver/e-component-listener.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Component listener + * + * Author: + * Rodrigo Moya <rodrigo@ximian.com> + * + * Copyright 2002, Ximian, Inc. + */ + +#ifndef __E_COMPONENT_LISTENER_H__ +#define __E_COMPONENT_LISTENER_H__ + +#include <glib-object.h> +#include <bonobo/Bonobo.h> + +G_BEGIN_DECLS + +#define E_COMPONENT_LISTENER_TYPE (e_component_listener_get_type ()) +#define E_COMPONENT_LISTENER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), E_COMPONENT_LISTENER_TYPE, EComponentListener)) +#define E_COMPONENT_LISTENER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), E_COMPONENT_LISTENER_TYPE, EComponentListenerClass)) +#define E_IS_COMPONENT_LISTENER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_COMPONENT_LISTENER_TYPE)) +#define E_IS_COMPONENT_LISTENER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_COMPONENT_LISTENER_TYPE)) + +typedef struct _EComponentListenerPrivate EComponentListenerPrivate; + +typedef struct { + GObject object; + EComponentListenerPrivate *priv; +} EComponentListener; + +typedef struct { + GObjectClass parent_class; + + void (* component_died) (EComponentListener *cl); +} EComponentListenerClass; + +GType e_component_listener_get_type (void); +EComponentListener *e_component_listener_new (Bonobo_Unknown comp); + +Bonobo_Unknown e_component_listener_get_component (EComponentListener *cl); +void e_component_listener_set_component (EComponentListener *cl, + Bonobo_Unknown comp); + +G_END_DECLS + +#endif diff --git a/libedataserver/e-data-server-marshal.c b/libedataserver/e-data-server-marshal.c new file mode 100644 index 000000000..daf7e3ff1 --- /dev/null +++ b/libedataserver/e-data-server-marshal.c @@ -0,0 +1,54 @@ +#include "e-data-server-marshal.h" + +#include <glib-object.h> + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_int +#define g_marshal_value_peek_flags(v) (v)->data[0].v_uint +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* NONE:NONE (e-data-server-marshal.list:1) */ + +/* NONE:OBJECT (e-data-server-marshal.list:2) */ + diff --git a/libedataserver/e-data-server-marshal.h b/libedataserver/e-data-server-marshal.h new file mode 100644 index 000000000..cc82af0f7 --- /dev/null +++ b/libedataserver/e-data-server-marshal.h @@ -0,0 +1,20 @@ + +#ifndef __e_data_server_marshal_MARSHAL_H__ +#define __e_data_server_marshal_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* NONE:NONE (e-data-server-marshal.list:1) */ +#define e_data_server_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID +#define e_data_server_marshal_NONE__NONE e_data_server_marshal_VOID__VOID + +/* NONE:OBJECT (e-data-server-marshal.list:2) */ +#define e_data_server_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT +#define e_data_server_marshal_NONE__OBJECT e_data_server_marshal_VOID__OBJECT + +G_END_DECLS + +#endif /* __e_data_server_marshal_MARSHAL_H__ */ + diff --git a/libedataserver/e-db3-utils.c b/libedataserver/e-db3-utils.c new file mode 100644 index 000000000..3326f32a6 --- /dev/null +++ b/libedataserver/e-db3-utils.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +#include "config.h" + +#include "e-db3-utils.h" + +#include <db.h> + +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <libgnome/gnome-util.h> + +#if DB_VERSION_MAJOR != 3 || \ + DB_VERSION_MINOR != 1 || \ + DB_VERSION_PATCH != 17 +#error Including wrong DB3. Need libdb 3.1.17. +#endif + +static char * +get_check_filename (const char *filename) +{ + return g_strdup_printf ("%s-upgrading", filename); +} + +static char * +get_copy_filename (const char *filename) +{ + return g_strdup_printf ("%s-copy", filename); +} + +static int +cp_file (const char *src, const char *dest) +{ + int i; + int o; + char buffer[1024]; + int length; + int place; + + i = open (src, O_RDONLY); + if (i == -1) + return -1; + o = creat (dest, S_IREAD | S_IWRITE); + if (o == -1) { + close (i); + return -1; + } + while (1) { + length = read (i, &buffer, sizeof (buffer)); + + if (length == 0) + break; + + if (length == -1) { + if (errno == EINTR) + continue; + else { + close (i); + close (o); + unlink (dest); + return -1; + } + } + + place = 0; + while (length != 0) { + int count; + count = write (o, buffer + place, length); + if (count == -1) { + if (errno == EINTR) + continue; + else { + close (i); + close (o); + unlink (dest); + return -1; + } + } + + length -= count; + place += count; + } + } + if (close (i)) + return -1; + if (close (o)) + return -1; + return 0; +} + +static int +touch_file (const char *file) +{ + int o; + o = creat (file, S_IREAD | S_IWRITE); + if (o == -1) + return -1; + + if (close (o) == -1) + return -1; + + return 0; +} + +static int +resume_upgrade (const char *filename, const char *copy_filename, const char *check_filename) +{ + DB *db; + int ret_val; + + ret_val = db_create (&db, NULL, 0); + + if (ret_val == 0) + ret_val = cp_file (copy_filename, filename); + + if (ret_val == 0) + ret_val = db->upgrade (db, filename, 0); + + if (ret_val == 0) + ret_val = unlink (check_filename); + if (ret_val == 0) + ret_val = unlink (copy_filename); + + db->close (db, 0); + + return ret_val; +} + +int +e_db3_utils_maybe_recover (const char *filename) +{ + int ret_val = 0; + char *copy_filename; + char *check_filename; + + copy_filename = get_copy_filename (filename); + check_filename = get_check_filename (filename); + + if (g_file_exists (check_filename)) { + ret_val = resume_upgrade(filename, copy_filename, check_filename); + } else if (g_file_exists (copy_filename)) { + unlink (copy_filename); + } + + g_free (copy_filename); + g_free (check_filename); + return ret_val; +} + +int +e_db3_utils_upgrade_format (const char *filename) +{ + char *copy_filename; + char *check_filename; + DB *db; + int ret_val; + + ret_val = db_create (&db, NULL, 0); + if (ret_val != 0) + return ret_val; + + copy_filename = get_copy_filename (filename); + check_filename = get_check_filename (filename); + + ret_val = cp_file (filename, copy_filename); + + if (ret_val == 0) + ret_val = touch_file (check_filename); + if (ret_val == 0) + ret_val = db->upgrade (db, filename, 0); + if (ret_val == 0) + ret_val = unlink (check_filename); + + if (ret_val == 0) + ret_val = unlink (copy_filename); + + db->close (db, 0); + + g_free (check_filename); + g_free (copy_filename); + return ret_val; +} diff --git a/libedataserver/e-db3-utils.h b/libedataserver/e-db3-utils.h new file mode 100644 index 000000000..a574e5917 --- /dev/null +++ b/libedataserver/e-db3-utils.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * db3 utils. + * + * Author: + * Chris Lahey <clahey@ximian.com> + * + * Copyright 2001, Ximian, Inc. + */ + +#ifndef __E_DB3_UTILS_H__ +#define __E_DB3_UTILS_H__ + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +int e_db3_utils_maybe_recover (const char *filename); +int e_db3_utils_upgrade_format (const char *filename); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! __E_DB3_UTILS_H__ */ + diff --git a/libedataserver/e-dbhash.c b/libedataserver/e-dbhash.c new file mode 100644 index 000000000..209798fbc --- /dev/null +++ b/libedataserver/e-dbhash.c @@ -0,0 +1,227 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Author: + * JP Rosevear (jpr@ximian.com) + * + * Copyright 2000, Ximian, Inc. + */ + +#include <config.h> + +#include "e-dbhash.h" + +#include <string.h> +#include <fcntl.h> +#include <db.h> +#include "md5-utils.h" + +#if DB_VERSION_MAJOR != 3 || \ + DB_VERSION_MINOR != 1 || \ + DB_VERSION_PATCH != 17 +#error Including wrong DB3. Need libdb 3.1.17. +#endif + +struct _EDbHashPrivate +{ + DB *db; +}; + +EDbHash * +e_dbhash_new (const char *filename) +{ + EDbHash *edbh; + DB *db; + int rv; + + int major, minor, patch; + + db_version (&major, &minor, &patch); + + if (major != 3 || + minor != 1 || + patch != 17) { + g_warning ("Wrong version of libdb."); + return NULL; + } + + /* Attempt to open the database */ + rv = db_create (&db, NULL, 0); + if (rv != 0) { + return NULL; + } + + rv = db->open (db, filename, NULL, DB_HASH, 0, 0666); + if (rv != 0) { + rv = db->open (db, filename, NULL, DB_HASH, DB_CREATE, 0666); + + if (rv != 0) + return NULL; + } + + edbh = g_new (EDbHash, 1); + edbh->priv = g_new (EDbHashPrivate, 1); + edbh->priv->db = db; + + return edbh; +} + +static void +string_to_dbt(const char *str, DBT *dbt) +{ + memset (dbt, 0, sizeof (DBT)); + dbt->data = (void*)str; + dbt->size = strlen (str) + 1; +} + +static void +md5_to_dbt(const char str[16], DBT *dbt) +{ + memset (dbt, 0, sizeof (DBT)); + dbt->data = (void*)str; + dbt->size = 16; +} + +void +e_dbhash_add (EDbHash *edbh, const gchar *key, const gchar *data) +{ + DB *db; + DBT dkey; + DBT ddata; + guchar local_hash[16]; + + g_return_if_fail (edbh != NULL); + g_return_if_fail (edbh->priv != NULL); + g_return_if_fail (edbh->priv->db != NULL); + g_return_if_fail (key != NULL); + g_return_if_fail (data != NULL); + + db = edbh->priv->db; + + /* Key dbt */ + string_to_dbt (key, &dkey); + + /* Data dbt */ + md5_get_digest (data, strlen (data), local_hash); + md5_to_dbt (local_hash, &ddata); + + /* Add to database */ + db->put (db, NULL, &dkey, &ddata, 0); +} + +void +e_dbhash_remove (EDbHash *edbh, const char *key) +{ + DB *db; + DBT dkey; + + g_return_if_fail (edbh != NULL); + g_return_if_fail (edbh->priv != NULL); + g_return_if_fail (key != NULL); + + db = edbh->priv->db; + + /* Key dbt */ + string_to_dbt (key, &dkey); + + /* Remove from database */ + db->del (db, NULL, &dkey, 0); +} + +void +e_dbhash_foreach_key (EDbHash *edbh, EDbHashFunc func, gpointer user_data) +{ + DB *db; + DBT dkey; + DBT ddata; + DBC *dbc; + int db_error = 0; + + g_return_if_fail (edbh != NULL); + g_return_if_fail (edbh->priv != NULL); + g_return_if_fail (func != NULL); + + db = edbh->priv->db; + + db_error = db->cursor (db, NULL, &dbc, 0); + + if (db_error != 0) { + return; + } + + memset(&dkey, 0, sizeof(DBT)); + memset(&ddata, 0, sizeof(DBT)); + db_error = dbc->c_get(dbc, &dkey, &ddata, DB_FIRST); + + while (db_error == 0) { + (*func) ((const char *)dkey.data, user_data); + + db_error = dbc->c_get(dbc, &dkey, &ddata, DB_NEXT); + } + dbc->c_close (dbc); +} + +EDbHashStatus +e_dbhash_compare (EDbHash *edbh, const char *key, const char *compare_data) +{ + DB *db; + DBT dkey; + DBT ddata; + guchar compare_hash[16]; + + g_return_val_if_fail (edbh != NULL, FALSE); + g_return_val_if_fail (edbh->priv != NULL, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (compare_hash != NULL, FALSE); + + db = edbh->priv->db; + + /* Key dbt */ + string_to_dbt (key, &dkey); + + /* Lookup in database */ + memset (&ddata, 0, sizeof (DBT)); + db->get (db, NULL, &dkey, &ddata, 0); + + /* Compare */ + if (ddata.data) { + md5_get_digest (compare_data, strlen (compare_data), compare_hash); + + if (memcmp (ddata.data, compare_hash, sizeof (guchar) * 16)) + return E_DBHASH_STATUS_DIFFERENT; + } else { + return E_DBHASH_STATUS_NOT_FOUND; + } + + return E_DBHASH_STATUS_SAME; +} + +void +e_dbhash_write (EDbHash *edbh) +{ + DB *db; + + g_return_if_fail (edbh != NULL); + g_return_if_fail (edbh->priv != NULL); + + db = edbh->priv->db; + + /* Flush database to disk */ + db->sync (db, 0); +} + +void +e_dbhash_destroy (EDbHash *edbh) +{ + DB *db; + + g_return_if_fail (edbh != NULL); + g_return_if_fail (edbh->priv != NULL); + + db = edbh->priv->db; + + /* Close datbase */ + db->close (db, 0); + + g_free (edbh->priv); + g_free (edbh); +} diff --git a/libedataserver/e-dbhash.h b/libedataserver/e-dbhash.h new file mode 100644 index 000000000..9772a60c4 --- /dev/null +++ b/libedataserver/e-dbhash.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Writes hashes that go to/from disk in db form. Hash keys are strings + * + * Author: + * JP Rosevear (jpr@ximian.com) + * + * Copyright 2000, Ximian, Inc. + */ + +#ifndef __E_DBHASH_H__ +#define __E_DBHASH_H__ + +#include <glib.h> + +typedef enum { + E_DBHASH_STATUS_SAME, + E_DBHASH_STATUS_DIFFERENT, + E_DBHASH_STATUS_NOT_FOUND, +} EDbHashStatus; + +typedef struct _EDbHash EDbHash; +typedef struct _EDbHashPrivate EDbHashPrivate; + +struct _EDbHash +{ + EDbHashPrivate *priv; +}; + +typedef void (*EDbHashFunc) (const char *key, gpointer user_data); + +EDbHash *e_dbhash_new (const char *filename); + +void e_dbhash_add (EDbHash *edbh, const char *key, const char *data); +void e_dbhash_remove (EDbHash *edbh, const char *key); + +EDbHashStatus e_dbhash_compare (EDbHash *edbh, const char *key, const char *compare_data); +void e_dbhash_foreach_key (EDbHash *edbh, EDbHashFunc func, gpointer user_data); + +void e_dbhash_write (EDbHash *edbh); + +void e_dbhash_destroy (EDbHash *edbh); + +#endif /* ! __E_DBHASH_H__ */ + diff --git a/libedataserver/e-iterator.c b/libedataserver/e-iterator.c new file mode 100644 index 000000000..7f736719b --- /dev/null +++ b/libedataserver/e-iterator.c @@ -0,0 +1,183 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Christopher James Lahey <clahey@umich.edu> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#include <config.h> + +#include "e-iterator.h" +#include "e-data-server-marshal.h" + +static void e_iterator_init (EIterator *card); +static void e_iterator_class_init (EIteratorClass *klass); + +#define PARENT_TYPE G_TYPE_OBJECT + +static GObjectClass *parent_class; + +enum { + INVALIDATE, + LAST_SIGNAL +}; + +static guint e_iterator_signals [LAST_SIGNAL] = { 0, }; + +/** + * e_iterator_get_type: + * @void: + * + * Registers the &EIterator class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EIterator class. + **/ +GType +e_iterator_get_type (void) +{ + static GType type = 0; + + if (! type) { + GTypeInfo info = { + sizeof (EIteratorClass), + NULL, /* base_class_init */ + NULL, /* base_class_finalize */ + (GClassInitFunc) e_iterator_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EIterator), + 0, /* n_preallocs */ + (GInstanceInitFunc) e_iterator_init + }; + + type = g_type_register_static (PARENT_TYPE, "EIterator", &info, 0); + } + + return type; +} + +static void +e_iterator_class_init (EIteratorClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS(klass); + + parent_class = g_type_class_ref (PARENT_TYPE); + + e_iterator_signals [INVALIDATE] = + g_signal_new ("invalidate", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EIteratorClass, invalidate), + NULL, NULL, + e_data_server_marshal_NONE__NONE, /* XXX need a new marshaller here */ + G_TYPE_NONE, 0); + + klass->invalidate = NULL; + klass->get = NULL; + klass->reset = NULL; + klass->last = NULL; + klass->next = NULL; + klass->prev = NULL; + klass->delete = NULL; + klass->insert = NULL; + klass->set = NULL; + klass->is_valid = NULL; +} + +/** + * e_iterator_init: + */ +static void +e_iterator_init (EIterator *card) +{ +} + +/* + * Virtual functions: + */ +const void * +e_iterator_get (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->get) + return E_ITERATOR_GET_CLASS(iterator)->get(iterator); + else + return NULL; +} + +void +e_iterator_reset (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->reset) + E_ITERATOR_GET_CLASS(iterator)->reset(iterator); +} + +void +e_iterator_last (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->last) + E_ITERATOR_GET_CLASS(iterator)->last(iterator); +} + +gboolean +e_iterator_next (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->next) + return E_ITERATOR_GET_CLASS(iterator)->next(iterator); + else + return FALSE; +} + +gboolean +e_iterator_prev (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->prev) + return E_ITERATOR_GET_CLASS(iterator)->prev(iterator); + else + return FALSE; +} + +void +e_iterator_delete (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->delete) + E_ITERATOR_GET_CLASS(iterator)->delete(iterator); +} + +void e_iterator_insert (EIterator *iterator, + const void *object, + gboolean before) +{ + if (E_ITERATOR_GET_CLASS(iterator)->insert) + E_ITERATOR_GET_CLASS(iterator)->insert(iterator, object, before); +} + +void +e_iterator_set (EIterator *iterator, + const void *object) +{ + if (E_ITERATOR_GET_CLASS(iterator)->set) + E_ITERATOR_GET_CLASS(iterator)->set(iterator, object); +} + +gboolean +e_iterator_is_valid (EIterator *iterator) +{ + if (E_ITERATOR_GET_CLASS(iterator)->is_valid) + return E_ITERATOR_GET_CLASS(iterator)->is_valid(iterator); + else + return FALSE; +} + +void +e_iterator_invalidate (EIterator *iterator) +{ + g_return_if_fail (iterator != NULL); + g_return_if_fail (E_IS_ITERATOR (iterator)); + + g_signal_emit (iterator, e_iterator_signals [INVALIDATE], 0); +} diff --git a/libedataserver/e-iterator.h b/libedataserver/e-iterator.h new file mode 100644 index 000000000..9dae97cc3 --- /dev/null +++ b/libedataserver/e-iterator.h @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#ifndef __E_ITERATOR_H__ +#define __E_ITERATOR_H__ + +#include <stdio.h> +#include <time.h> +#include <glib.h> +#include <glib-object.h> + +#define E_TYPE_ITERATOR (e_iterator_get_type ()) +#define E_ITERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_ITERATOR, EIterator)) +#define E_ITERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_ITERATOR, EIteratorClass)) +#define E_IS_ITERATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_ITERATOR)) +#define E_IS_ITERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_ITERATOR)) +#define E_ITERATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_ITERATOR, EIteratorClass)) + +typedef struct _EIterator EIterator; +typedef struct _EIteratorClass EIteratorClass; + +struct _EIterator { + GObject object; +}; + +struct _EIteratorClass { + GObjectClass parent_class; + + /* Signals */ + void (*invalidate) (EIterator *iterator); + + /* Virtual functions */ + const void * (*get) (EIterator *iterator); + void (*reset) (EIterator *iterator); + void (*last) (EIterator *iterator); + gboolean (*next) (EIterator *iterator); + gboolean (*prev) (EIterator *iterator); + void (*delete) (EIterator *iterator); + void (*insert) (EIterator *iterator, + const void *object, + gboolean before); + void (*set) (EIterator *iterator, + const void *object); + gboolean (*is_valid) (EIterator *iterator); +}; + +const void *e_iterator_get (EIterator *iterator); +void e_iterator_reset (EIterator *iterator); +void e_iterator_last (EIterator *iterator); +gboolean e_iterator_next (EIterator *iterator); +gboolean e_iterator_prev (EIterator *iterator); +void e_iterator_delete (EIterator *iterator); +void e_iterator_insert (EIterator *iterator, + const void *object, + gboolean before); +void e_iterator_set (EIterator *iterator, + const void *object); +gboolean e_iterator_is_valid (EIterator *iterator); + +void e_iterator_invalidate (EIterator *iterator); + +/* Standard Glib function */ +GType e_iterator_get_type (void); + +#endif /* ! __E_ITERATOR_H__ */ diff --git a/libedataserver/e-list-iterator.c b/libedataserver/e-list-iterator.c new file mode 100644 index 000000000..e801c22ef --- /dev/null +++ b/libedataserver/e-list-iterator.c @@ -0,0 +1,249 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Christopher James Lahey <clahey@umich.edu> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#include <config.h> + +#include "e-list-iterator.h" +#include "e-list.h" + + +static void e_list_iterator_init (EListIterator *list); +static void e_list_iterator_class_init (EListIteratorClass *klass); + +static void e_list_iterator_invalidate (EIterator *iterator); +static gboolean e_list_iterator_is_valid (EIterator *iterator); +static void e_list_iterator_set (EIterator *iterator, + const void *object); +static void e_list_iterator_delete (EIterator *iterator); +static void e_list_iterator_insert (EIterator *iterator, + const void *object, + gboolean before); +static gboolean e_list_iterator_prev (EIterator *iterator); +static gboolean e_list_iterator_next (EIterator *iterator); +static void e_list_iterator_reset (EIterator *iterator); +static void e_list_iterator_last (EIterator *iterator); +static const void *e_list_iterator_get (EIterator *iterator); +static void e_list_iterator_dispose (GObject *object); + +#define PARENT_TYPE E_TYPE_ITERATOR + +static EIteratorClass *parent_class; + +/** + * e_list_iterator_get_type: + * @void: + * + * Registers the &EListIterator class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EListIterator class. + **/ +GType +e_list_iterator_get_type (void) +{ + static GType type = 0; + + if (! type) { + GTypeInfo info = { + sizeof (EListIteratorClass), + NULL, /* base_class_init */ + NULL, /* base_class_finalize */ + (GClassInitFunc) e_list_iterator_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EListIterator), + 0, /* n_preallocs */ + (GInstanceInitFunc) e_list_iterator_init + }; + + type = g_type_register_static (PARENT_TYPE, "EListIterator", &info, 0); + } + + return type; +} + +static void +e_list_iterator_class_init (EListIteratorClass *klass) +{ + GObjectClass *object_class; + EIteratorClass *iterator_class; + + object_class = G_OBJECT_CLASS(klass); + iterator_class = E_ITERATOR_CLASS(klass); + + parent_class = g_type_class_ref (PARENT_TYPE); + + object_class->dispose = e_list_iterator_dispose; + + iterator_class->invalidate = e_list_iterator_invalidate; + iterator_class->get = e_list_iterator_get; + iterator_class->reset = e_list_iterator_reset; + iterator_class->last = e_list_iterator_last; + iterator_class->next = e_list_iterator_next; + iterator_class->prev = e_list_iterator_prev; + iterator_class->delete = e_list_iterator_delete; + iterator_class->insert = e_list_iterator_insert; + iterator_class->set = e_list_iterator_set; + iterator_class->is_valid = e_list_iterator_is_valid; +} + + + +/** + * e_list_iterator_init: + */ +static void +e_list_iterator_init (EListIterator *list) +{ +} + +EIterator * +e_list_iterator_new (EList *list) +{ + EListIterator *iterator = g_object_new (E_TYPE_LIST_ITERATOR, NULL); + + iterator->list = list; + g_object_ref(list); + iterator->iterator = list->list; + + return E_ITERATOR(iterator); +} + +/* + * Virtual functions: + */ +static void +e_list_iterator_dispose (GObject *object) +{ + EListIterator *iterator = E_LIST_ITERATOR(object); + e_list_remove_iterator(iterator->list, E_ITERATOR(iterator)); + g_object_unref(iterator->list); + + if (G_OBJECT_CLASS (parent_class)->dispose) + (* G_OBJECT_CLASS (parent_class)->dispose) (object); +} + +static const void * +e_list_iterator_get (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + if (iterator->iterator) + return iterator->iterator->data; + else + return NULL; +} + +static void +e_list_iterator_reset (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + iterator->iterator = iterator->list->list; +} + +static void +e_list_iterator_last (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + iterator->iterator = g_list_last(iterator->list->list); +} + +static gboolean +e_list_iterator_next (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + if (iterator->iterator) + iterator->iterator = g_list_next(iterator->iterator); + else + iterator->iterator = iterator->list->list; + return (iterator->iterator != NULL); +} + +static gboolean +e_list_iterator_prev (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + if (iterator->iterator) + iterator->iterator = g_list_previous(iterator->iterator); + else + iterator->iterator = g_list_last(iterator->list->list); + return (iterator->iterator != NULL); +} + +static void +e_list_iterator_insert (EIterator *_iterator, + const void *object, + gboolean before) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + void *data; + if (iterator->list->copy) + data = iterator->list->copy(object, iterator->list->closure); + else + data = (void *) object; + if (iterator->iterator) { + if (before) { + iterator->list->list = g_list_first(g_list_prepend(iterator->iterator, data)); + iterator->iterator = iterator->iterator->prev; + } else { + if (iterator->iterator->next) + g_list_prepend(iterator->iterator->next, data); + else + g_list_append(iterator->iterator, data); + iterator->iterator = iterator->iterator->next; + } + e_list_invalidate_iterators(iterator->list, E_ITERATOR(iterator)); + } else { + if (before) { + iterator->list->list = g_list_append(iterator->list->list, data); + iterator->iterator = g_list_last(iterator->list->list); + } else { + iterator->list->list = g_list_prepend(iterator->list->list, data); + iterator->iterator = iterator->list->list; + } + e_list_invalidate_iterators(iterator->list, E_ITERATOR(iterator)); + } +} + +static void +e_list_iterator_delete (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + if (iterator->iterator) { + e_list_remove_link (iterator->list, iterator->iterator); + } +} + +static void +e_list_iterator_set (EIterator *_iterator, + const void *object) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + if (iterator->iterator) { + if (iterator->list->free) + iterator->list->free(iterator->iterator->data, iterator->list->closure); + if (iterator->list->copy) + iterator->iterator->data = iterator->list->copy(object, iterator->list->closure); + else + iterator->iterator->data = (void *) object; + } +} + +static gboolean +e_list_iterator_is_valid (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + return iterator->iterator != NULL; +} + +static void +e_list_iterator_invalidate (EIterator *_iterator) +{ + EListIterator *iterator = E_LIST_ITERATOR(_iterator); + iterator->iterator = NULL; +} diff --git a/libedataserver/e-list-iterator.h b/libedataserver/e-list-iterator.h new file mode 100644 index 000000000..e2b2e563b --- /dev/null +++ b/libedataserver/e-list-iterator.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#ifndef __E_LIST_ITERATOR_H__ +#define __E_LIST_ITERATOR_H__ + +typedef struct _EListIterator EListIterator; +typedef struct _EListIteratorClass EListIteratorClass; + +#include <stdio.h> +#include <time.h> +#include <glib.h> +#include <glib-object.h> + +#include <libedataserver/e-iterator.h> +#include <libedataserver/e-list.h> + +#define E_TYPE_LIST_ITERATOR (e_list_iterator_get_type ()) +#define E_LIST_ITERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_LIST_ITERATOR, EListIterator)) +#define E_LIST_ITERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_LIST_ITERATOR, EListIteratorClass)) +#define E_IS_LIST_ITERATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_LIST_ITERATOR)) +#define E_IS_LIST_ITERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_LIST_ITERATOR)) + +struct _EListIterator { + EIterator parent; + + EList *list; + GList *iterator; +}; + +struct _EListIteratorClass { + EIteratorClass parent_class; +}; + +EIterator *e_list_iterator_new (EList *list); + +/* Standard Glib function */ +GType e_list_iterator_get_type (void); + +#endif /* ! __E_LIST_ITERATOR_H__ */ diff --git a/libedataserver/e-list.c b/libedataserver/e-list.c new file mode 100644 index 000000000..f1b190b16 --- /dev/null +++ b/libedataserver/e-list.c @@ -0,0 +1,191 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Christopher James Lahey <clahey@umich.edu> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#include <config.h> + +#include "e-list.h" +#include "e-list-iterator.h" + +static void e_list_init (EList *list); +static void e_list_class_init (EListClass *klass); +static void e_list_dispose (GObject *object); + +static GObjectClass *parent_class; + +/** + * e_list_get_type: + * @void: + * + * Registers the &EList class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &EList class. + **/ +GType +e_list_get_type (void) +{ + static GType type = 0; + + if (! type) { + GTypeInfo info = { + sizeof (EListClass), + NULL, /* base_class_init */ + NULL, /* base_class_finalize */ + (GClassInitFunc) e_list_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EList), + 0, /* n_preallocs */ + (GInstanceInitFunc) e_list_init + }; + + type = g_type_register_static (G_TYPE_OBJECT, "EList", &info, 0); + } + + return type; +} + +static void +e_list_class_init (EListClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS(klass); + + parent_class = g_type_class_ref (G_TYPE_OBJECT); + + object_class->dispose = e_list_dispose; +} + +/** + * e_list_init: + */ +static void +e_list_init (EList *list) +{ + list->list = NULL; + list->iterators = NULL; +} + +EList * +e_list_new (EListCopyFunc copy, EListFreeFunc free, void *closure) +{ + EList *list = g_object_new (E_TYPE_LIST, NULL); + e_list_construct (list, copy, free, closure); + return list; +} + +void +e_list_construct (EList *list, EListCopyFunc copy, EListFreeFunc free, void *closure) +{ + list->copy = copy; + list->free = free; + list->closure = closure; +} + +EList * +e_list_duplicate (EList *old) +{ + EList *list = g_object_new (E_TYPE_LIST, NULL); + + list->copy = old->copy; + list->free = old->free; + list->closure = old->closure; + list->list = g_list_copy(old->list); + if (list->copy) { + GList *listlist; + for (listlist = list->list; listlist; listlist = listlist->next) { + listlist->data = list->copy (listlist->data, list->closure); + } + } + return list; +} + +EIterator * +e_list_get_iterator (EList *list) +{ + EIterator *iterator = e_list_iterator_new(list); + list->iterators = g_list_append(list->iterators, iterator); + return iterator; +} + +int +e_list_length (EList *list) +{ + return g_list_length(list->list); +} + +void +e_list_append (EList *list, const void *data) +{ + e_list_invalidate_iterators(list, NULL); + if (list->copy) + list->list = g_list_append(list->list, list->copy(data, list->closure)); + else + list->list = g_list_append(list->list, (void *) data); +} + +void +e_list_remove (EList *list, const void *data) +{ + GList *link; + link = g_list_find (list->list, data); + if (link) + e_list_remove_link(list, link); +} + +void +e_list_invalidate_iterators (EList *list, EIterator *skip) +{ + GList *iterators = list->iterators; + for (; iterators; iterators = iterators->next) { + if (iterators->data != skip) { + e_iterator_invalidate(E_ITERATOR(iterators->data)); + } + } +} + +/* FIXME: This doesn't work properly if the iterator is the first + iterator in the list. Well, the iterator doesn't continue on after + the next time next is called, at least. */ +void +e_list_remove_link (EList *list, GList *link) +{ + GList *iterators = list->iterators; + for (; iterators; iterators = iterators->next) { + if (((EListIterator *)iterators->data)->iterator == link) { + e_iterator_prev(iterators->data); + } + } + if (list->free) + list->free(link->data, list->closure); + list->list = g_list_remove_link(list->list, link); + g_list_free_1(link); +} + +void +e_list_remove_iterator (EList *list, EIterator *iterator) +{ + list->iterators = g_list_remove(list->iterators, iterator); +} + +/* + * Virtual functions + */ +static void +e_list_dispose (GObject *object) +{ + EList *list = E_LIST(object); + if (list->free) + g_list_foreach(list->list, (GFunc) list->free, list->closure); + g_list_free(list->list); + + (* G_OBJECT_CLASS (parent_class)->dispose) (object); +} + diff --git a/libedataserver/e-list.h b/libedataserver/e-list.h new file mode 100644 index 000000000..733bf49e9 --- /dev/null +++ b/libedataserver/e-list.h @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 2000 Ximian, Inc. + * Copyright (C) 1999 The Free Software Foundation + */ + +#ifndef __E_LIST_H__ +#define __E_LIST_H__ + +typedef struct _EList EList; +typedef struct _EListClass EListClass; + +#include <stdio.h> +#include <time.h> +#include <glib.h> +#include <glib-object.h> +#include <libedataserver/e-list-iterator.h> + +#define E_TYPE_LIST (e_list_get_type ()) +#define E_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_LIST, EList)) +#define E_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_LIST, EListClass)) +#define E_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_LIST)) +#define E_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_LIST)) +#define E_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_LIST, EListClass)) + +typedef void *(*EListCopyFunc) (const void *data, void *closure); +typedef void (*EListFreeFunc) (void *data, void *closure); + +struct _EList { + GObject object; + GList *list; + GList *iterators; + EListCopyFunc copy; + EListFreeFunc free; + void *closure; +}; + +struct _EListClass { + GObjectClass parent_class; +}; + +EList *e_list_new (EListCopyFunc copy, + EListFreeFunc free, + void *closure); +void e_list_construct (EList *list, + EListCopyFunc copy, + EListFreeFunc free, + void *closure); +EList *e_list_duplicate (EList *list); +EIterator *e_list_get_iterator (EList *list); +void e_list_append (EList *list, + const void *data); +void e_list_remove (EList *list, + const void *data); +int e_list_length (EList *list); + +/* For iterators to call. */ +void e_list_remove_link (EList *list, + GList *link); +void e_list_remove_iterator (EList *list, + EIterator *iterator); +void e_list_invalidate_iterators (EList *list, + EIterator *skip); + +/* Standard Glib function */ +GType e_list_get_type (void); + +#endif /* ! __E_LIST_H__ */ diff --git a/libedataserver/e-memory.c b/libedataserver/e-memory.c new file mode 100644 index 000000000..cf32147d7 --- /dev/null +++ b/libedataserver/e-memory.c @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 2000, 2001 Ximian Inc. + * + * Authors: Michael Zucchi <notzed@ximian.com> + * Jacob Berkman <jacob@ximian.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 "e-memory.h" + +#include <string.h> /* memset() */ +#include <stdlib.h> /* alloca() */ +#include <glib.h> + +#define s(x) /* strv debug */ +#define p(x) /* poolv debug */ +#define p2(x) /* poolv assertion checking */ + +/*#define MALLOC_CHECK*/ + +/*#define PROFILE_POOLV*/ + +#ifdef PROFILE_POOLV +#include <time.h> +#define pp(x) x +#else +#define pp(x) +#endif + +/*#define TIMEIT*/ + +#ifdef TIMEIT +#include <sys/time.h> +#include <unistd.h> + +struct timeval timeit_start; + +static time_start(const char *desc) +{ + gettimeofday(&timeit_start, NULL); + printf("starting: %s\n", desc); +} + +static time_end(const char *desc) +{ + unsigned long diff; + struct timeval end; + + gettimeofday(&end, NULL); + diff = end.tv_sec * 1000 + end.tv_usec/1000; + diff -= timeit_start.tv_sec * 1000 + timeit_start.tv_usec/1000; + printf("%s took %ld.%03ld seconds\n", + desc, diff / 1000, diff % 1000); +} +#else +#define time_start(x) +#define time_end(x) +#endif + +#ifdef MALLOC_CHECK +#include <mcheck.h> +#include <stdio.h> +static void +checkmem(void *p) +{ + if (p) { + int status = mprobe(p); + + switch (status) { + case MCHECK_HEAD: + printf("Memory underrun at %p\n", p); + abort(); + case MCHECK_TAIL: + printf("Memory overrun at %p\n", p); + abort(); + case MCHECK_FREE: + printf("Double free %p\n", p); + abort(); + } + } +} +#define MPROBE(x) checkmem((void *)(x)) +#else +#define MPROBE(x) +#endif + +/* mempool class */ + +#define STRUCT_ALIGN (4) + +typedef struct _MemChunkFreeNode { + struct _MemChunkFreeNode *next; + unsigned int atoms; +} MemChunkFreeNode; + +typedef struct _EMemChunk { + unsigned int blocksize; /* number of atoms in a block */ + unsigned int atomsize; /* size of each atom */ + GPtrArray *blocks; /* blocks of raw memory */ + struct _MemChunkFreeNode *free; +} MemChunk; + +/** + * e_memchunk_new: + * @atomcount: The number of atoms stored in a single malloc'd block of memory. + * @atomsize: The size of each allocation. + * + * Create a new memchunk header. Memchunks are an efficient way to allocate + * and deallocate identical sized blocks of memory quickly, and space efficiently. + * + * e_memchunks are effectively the same as gmemchunks, only faster (much), and + * they use less memory overhead for housekeeping. + * + * Return value: The new header. + **/ +MemChunk *e_memchunk_new(int atomcount, int atomsize) +{ + MemChunk *m = g_malloc(sizeof(*m)); + + m->blocksize = atomcount; + m->atomsize = MAX(atomsize, sizeof(MemChunkFreeNode)); + m->blocks = g_ptr_array_new(); + m->free = NULL; + + return m; +} + +/** + * memchunk_alloc: + * @m: + * + * Allocate a new atom size block of memory from a memchunk. + **/ +void *e_memchunk_alloc(MemChunk *m) +{ + char *b; + MemChunkFreeNode *f; + void *mem; + + f = m->free; + if (f) { + f->atoms--; + if (f->atoms > 0) { + mem = ((char *)f) + (f->atoms*m->atomsize); + } else { + mem = f; + m->free = m->free->next; + } + return mem; + } else { + b = g_malloc(m->blocksize * m->atomsize); + g_ptr_array_add(m->blocks, b); + f = (MemChunkFreeNode *)&b[m->atomsize]; + f->atoms = m->blocksize-1; + f->next = NULL; + m->free = f; + return b; + } +} + +void *e_memchunk_alloc0(EMemChunk *m) +{ + void *mem; + + mem = e_memchunk_alloc(m); + memset(mem, 0, m->atomsize); + + return mem; +} + +/** + * e_memchunk_free: + * @m: + * @mem: Address of atom to free. + * + * Free a single atom back to the free pool of atoms in the given + * memchunk. + **/ +void +e_memchunk_free(MemChunk *m, void *mem) +{ + MemChunkFreeNode *f; + + /* put the location back in the free list. If we knew if the preceeding or following + cells were free, we could merge the free nodes, but it doesn't really add much */ + f = mem; + f->next = m->free; + m->free = f; + f->atoms = 1; + + /* we could store the free list sorted - we could then do the above, and also + probably improve the locality of reference properties for the allocator */ + /* and it would simplify some other algorithms at that, but slow this one down + significantly */ +} + +/** + * e_memchunk_empty: + * @m: + * + * Clean out the memchunk buffers. Marks all allocated memory as free blocks, + * but does not give it back to the system. Can be used if the memchunk + * is to be used repeatedly. + **/ +void +e_memchunk_empty(MemChunk *m) +{ + int i; + MemChunkFreeNode *f, *h = NULL; + + for (i=0;i<m->blocks->len;i++) { + f = (MemChunkFreeNode *)m->blocks->pdata[i]; + f->atoms = m->blocksize; + f->next = h; + h = f; + } + m->free = h; +} + +struct _cleaninfo { + struct _cleaninfo *next; + char *base; + int count; + int size; /* just so tree_search has it, sigh */ +}; + +static int tree_compare(struct _cleaninfo *a, struct _cleaninfo *b) +{ + if (a->base < b->base) + return -1; + else if (a->base > b->base) + return 1; + return 0; +} + +static int tree_search(struct _cleaninfo *a, char *mem) +{ + if (a->base <= mem) { + if (mem < &a->base[a->size]) + return 0; + return 1; + } + return -1; +} + +/** + * e_memchunk_clean: + * @m: + * + * Scan all empty blocks and check for blocks which can be free'd + * back to the system. + * + * This routine may take a while to run if there are many allocated + * memory blocks (if the total number of allocations is many times + * greater than atomcount). + **/ +void +e_memchunk_clean(MemChunk *m) +{ + GTree *tree; + int i; + MemChunkFreeNode *f; + struct _cleaninfo *ci, *hi = NULL; + + f = m->free; + if (m->blocks->len == 0 || f == NULL) + return; + + /* first, setup the tree/list so we can map free block addresses to block addresses */ + tree = g_tree_new((GCompareFunc)tree_compare); + for (i=0;i<m->blocks->len;i++) { + ci = alloca(sizeof(*ci)); + ci->count = 0; + ci->base = m->blocks->pdata[i]; + ci->size = m->blocksize * m->atomsize; + g_tree_insert(tree, ci, ci); + ci->next = hi; + hi = ci; + } + + /* now, scan all free nodes, and count them in their tree node */ + while (f) { + ci = g_tree_search(tree, (GCompareFunc) tree_search, f); + if (ci) { + ci->count += f->atoms; + } else { + g_warning("error, can't find free node in memory block\n"); + } + f = f->next; + } + + /* if any nodes are all free, free & unlink them */ + ci = hi; + while (ci) { + if (ci->count == m->blocksize) { + MemChunkFreeNode *prev = NULL; + + f = m->free; + while (f) { + if (tree_search (ci, (void *) f) == 0) { + /* prune this node from our free-node list */ + if (prev) + prev->next = f->next; + else + m->free = f->next; + } else { + prev = f; + } + + f = f->next; + } + + g_ptr_array_remove_fast(m->blocks, ci->base); + g_free(ci->base); + } + ci = ci->next; + } + + g_tree_destroy(tree); +} + +/** + * e_memchunk_destroy: + * @m: + * + * Free the memchunk header, and all associated memory. + **/ +void +e_memchunk_destroy(MemChunk *m) +{ + int i; + + if (m == NULL) + return; + + for (i=0;i<m->blocks->len;i++) + g_free(m->blocks->pdata[i]); + g_ptr_array_free(m->blocks, TRUE); + g_free(m); +} + +typedef struct _MemPoolNode { + struct _MemPoolNode *next; + + int free; + char data[1]; +} MemPoolNode; + +typedef struct _MemPoolThresholdNode { + struct _MemPoolThresholdNode *next; + char data[1]; +} MemPoolThresholdNode; + +typedef struct _EMemPool { + int blocksize; + int threshold; + unsigned int align; + struct _MemPoolNode *blocks; + struct _MemPoolThresholdNode *threshold_blocks; +} MemPool; + +/* a pool of mempool header blocks */ +static MemChunk *mempool_memchunk; +#ifdef G_THREADS_ENABLED +static GStaticMutex mempool_mutex = G_STATIC_MUTEX_INIT; +#endif + +/** + * e_mempool_new: + * @blocksize: The base blocksize to use for all system alocations. + * @threshold: If the allocation exceeds the threshold, then it is + * allocated separately and stored in a separate list. + * @flags: Alignment options: E_MEMPOOL_ALIGN_STRUCT uses native + * struct alignment, E_MEMPOOL_ALIGN_WORD aligns to 16 bits (2 bytes), + * and E_MEMPOOL_ALIGN_BYTE aligns to the nearest byte. The default + * is to align to native structures. + * + * Create a new mempool header. Mempools can be used to efficiently + * allocate data which can then be freed as a whole. + * + * Mempools can also be used to efficiently allocate arbitrarily + * aligned data (such as strings) without incurring the space overhead + * of aligning each allocation (which is not required for strings). + * + * However, each allocation cannot be freed individually, only all + * or nothing. + * + * Return value: + **/ +MemPool *e_mempool_new(int blocksize, int threshold, EMemPoolFlags flags) +{ + MemPool *pool; + +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&mempool_mutex); +#endif + if (mempool_memchunk == NULL) { + mempool_memchunk = e_memchunk_new(8, sizeof(MemPool)); + } + pool = e_memchunk_alloc(mempool_memchunk); +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&mempool_mutex); +#endif + if (threshold >= blocksize) + threshold = blocksize * 2 / 3; + pool->blocksize = blocksize; + pool->threshold = threshold; + pool->blocks = NULL; + pool->threshold_blocks = NULL; + + switch (flags & E_MEMPOOL_ALIGN_MASK) { + case E_MEMPOOL_ALIGN_STRUCT: + default: + pool->align = STRUCT_ALIGN-1; + break; + case E_MEMPOOL_ALIGN_WORD: + pool->align = 2-1; + break; + case E_MEMPOOL_ALIGN_BYTE: + pool->align = 1-1; + } + return pool; +} + +/** + * e_mempool_alloc: + * @pool: + * @size: + * + * Allocate a new data block in the mempool. Size will + * be rounded up to the mempool's alignment restrictions + * before being used. + **/ +void *e_mempool_alloc(MemPool *pool, register int size) +{ + size = (size + pool->align) & (~(pool->align)); + if (size>=pool->threshold) { + MemPoolThresholdNode *n; + + n = g_malloc(sizeof(*n) - sizeof(char) + size); + n->next = pool->threshold_blocks; + pool->threshold_blocks = n; + return &n->data[0]; + } else { + register MemPoolNode *n; + + n = pool->blocks; + if (n && n->free >= size) { + n->free -= size; + return &n->data[n->free]; + } + + /* maybe we could do some sort of the free blocks based on size, but + it doubt its worth it at all */ + + n = g_malloc(sizeof(*n) - sizeof(char) + pool->blocksize); + n->next = pool->blocks; + pool->blocks = n; + n->free = pool->blocksize - size; + return &n->data[n->free]; + } +} + +char *e_mempool_strdup(EMemPool *pool, const char *str) +{ + char *out; + + out = e_mempool_alloc(pool, strlen(str)+1); + strcpy(out, str); + + return out; +} + +/** + * e_mempool_flush: + * @pool: + * @freeall: Free all system allocated blocks as well. + * + * Flush used memory and mark allocated blocks as free. + * + * If @freeall is #TRUE, then all allocated blocks are free'd + * as well. Otherwise only blocks above the threshold are + * actually freed, and the others are simply marked as empty. + **/ +void e_mempool_flush(MemPool *pool, int freeall) +{ + MemPoolThresholdNode *tn, *tw; + MemPoolNode *pw, *pn; + + tw = pool->threshold_blocks; + while (tw) { + tn = tw->next; + g_free(tw); + tw = tn; + } + pool->threshold_blocks = NULL; + + if (freeall) { + pw = pool->blocks; + while (pw) { + pn = pw->next; + g_free(pw); + pw = pn; + } + pool->blocks = NULL; + } else { + pw = pool->blocks; + while (pw) { + pw->free = pool->blocksize; + pw = pw->next; + } + } +} + +/** + * e_mempool_destroy: + * @pool: + * + * Free all memory associated with a mempool. + **/ +void e_mempool_destroy(MemPool *pool) +{ + if (pool) { + e_mempool_flush(pool, 1); + e_memchunk_free(mempool_memchunk, pool); + } +} + + +/* + string array classes +*/ + +#define STRV_UNPACKED ((unsigned char)(~0)) + +struct _EStrv { + unsigned char length; /* how many entries we have (or the token STRV_UNPACKED) */ + char data[1]; /* data follows */ +}; + +struct _s_strv_string { + char *string; /* the string to output */ + char *free; /* a string to free, if we referenced it */ +}; + +struct _e_strvunpacked { + unsigned char type; /* we overload last to indicate this is unpacked */ + MemPool *pool; /* pool of memory for strings */ + struct _EStrv *source; /* if we were converted from a packed one, keep the source around for a while */ + unsigned int length; + struct _s_strv_string strings[1]; /* the string array data follows */ +}; + +/** + * e_strv_new: + * @size: The number of elements in the strv. Currently this is limited + * to 254 elements. + * + * Create a new strv (string array) header. strv's can be used to + * create and work with arrays of strings that can then be compressed + * into a space-efficient static structure. This is useful + * where a number of strings are to be stored for lookup, and not + * generally edited afterwards. + * + * The size limit is currently 254 elements. This will probably not + * change as arrays of this size suffer significant performance + * penalties when looking up strings with high indices. + * + * Return value: + **/ +struct _EStrv * +e_strv_new(int size) +{ + struct _e_strvunpacked *s; + + g_assert(size<255); + + s = g_malloc(sizeof(*s) + (size-1)*sizeof(s->strings[0])); + s(printf("new strv=%p, size = %d bytes\n", s, sizeof(*s) + (size-1)*sizeof(char *))); + s->type = STRV_UNPACKED; + s->pool = NULL; + s->length = size; + s->source = NULL; + memset(s->strings, 0, size*sizeof(s->strings[0])); + + return (struct _EStrv *)s; +} + +static struct _e_strvunpacked * +strv_unpack(struct _EStrv *strv) +{ + struct _e_strvunpacked *s; + register char *p; + int i; + + s(printf("unpacking\n")); + + s = (struct _e_strvunpacked *)e_strv_new(strv->length); + p = strv->data; + for (i=0;i<s->length;i++) { + if (i>0) + while (*p++) + ; + s->strings[i].string = p; + } + s->source = strv; + s->type = STRV_UNPACKED; + + return s; +} + +/** + * e_strv_set_ref: + * @strv: + * @index: + * @str: + * + * Set a string array element by reference. The string + * is not copied until the array is packed. + * + * If @strv has been packed, then it is unpacked ready + * for more inserts, and should be packed again once finished with. + * The memory used by the original @strv is not freed until + * the new strv is packed, or freed itself. + * + * Return value: A new EStrv if the strv has already + * been packed, otherwise @strv. + **/ +struct _EStrv * +e_strv_set_ref(struct _EStrv *strv, int index, char *str) +{ + struct _e_strvunpacked *s; + + s(printf("set ref %d '%s'\nawkmeharder: %s\n ", index, str, str)); + + if (strv->length != STRV_UNPACKED) + s = strv_unpack(strv); + else + s = (struct _e_strvunpacked *)strv; + + g_assert(index>=0 && index < s->length); + + s->strings[index].string = str; + + return (struct _EStrv *)s; +} + +/** + * e_strv_set_ref_free: + * @strv: + * @index: + * @str: + * + * Set a string by reference, similar to set_ref, but also + * free the string when finished with it. The string + * is not copied until the strv is packed, and not at + * all if the index is overwritten. + * + * Return value: @strv if already unpacked, otherwise an packed + * EStrv. + **/ +struct _EStrv * +e_strv_set_ref_free(struct _EStrv *strv, int index, char *str) +{ + struct _e_strvunpacked *s; + + s(printf("set ref %d '%s'\nawkmeevenharder: %s\n ", index, str, str)); + + if (strv->length != STRV_UNPACKED) + s = strv_unpack(strv); + else + s = (struct _e_strvunpacked *)strv; + + g_assert(index>=0 && index < s->length); + + s->strings[index].string = str; + if (s->strings[index].free) + g_free(s->strings[index].free); + s->strings[index].free = str; + + return (struct _EStrv *)s; +} + +/** + * e_strv_set: + * @strv: + * @index: + * @str: + * + * Set a string array reference. The string @str is copied + * into the string array at location @index. + * + * If @strv has been packed, then it is unpacked ready + * for more inserts, and should be packed again once finished with. + * + * Return value: A new EStrv if the strv has already + * been packed, otherwise @strv. + **/ +struct _EStrv * +e_strv_set(struct _EStrv *strv, int index, const char *str) +{ + struct _e_strvunpacked *s; + + s(printf("set %d '%s'\n", index, str)); + + if (strv->length != STRV_UNPACKED) + s = strv_unpack(strv); + else + s = (struct _e_strvunpacked *)strv; + + g_assert(index>=0 && index < s->length); + + if (s->pool == NULL) + s->pool = e_mempool_new(1024, 512, E_MEMPOOL_ALIGN_BYTE); + + s->strings[index].string = e_mempool_alloc(s->pool, strlen(str)+1); + strcpy(s->strings[index].string, str); + + return (struct _EStrv *)s; +} + +/** + * e_strv_pack: + * @strv: + * + * Pack the @strv into a space efficient structure for later lookup. + * + * All strings are packed into a single allocated block, separated + * by single \0 characters, together with a count byte. + * + * Return value: + **/ +struct _EStrv * +e_strv_pack(struct _EStrv *strv) +{ + struct _e_strvunpacked *s; + int len, i; + register char *src, *dst; + + if (strv->length == STRV_UNPACKED) { + s = (struct _e_strvunpacked *)strv; + + s(printf("packing string\n")); + + len = 0; + for (i=0;i<s->length;i++) + len += s->strings[i].string?strlen(s->strings[i].string)+1:1; + + strv = g_malloc(sizeof(*strv) + len); + s(printf("allocating strv=%p, size = %d\n", strv, sizeof(*strv)+len)); + strv->length = s->length; + dst = strv->data; + for (i=0;i<s->length;i++) { + if ((src = s->strings[i].string)) { + while ((*dst++ = *src++)) + ; + } else { + *dst++ = 0; + } + } + e_strv_destroy((struct _EStrv *)s); + } + return strv; +} + +/** + * e_strv_get: + * @strv: + * @index: + * + * Retrieve a string by index. This function works + * identically on both packed and unpacked strv's, although + * may be much slower on a packed strv. + * + * Return value: + **/ +char * +e_strv_get(struct _EStrv *strv, int index) +{ + struct _e_strvunpacked *s; + char *p; + + if (strv->length != STRV_UNPACKED) { + g_assert(index>=0 && index < strv->length); + p = strv->data; + while (index > 0) { + while (*p++ != 0) + ; + index--; + } + return p; + } else { + s = (struct _e_strvunpacked *)strv; + g_assert(index>=0 && index < s->length); + return s->strings[index].string?s->strings[index].string:""; + } +} + +/** + * e_strv_destroy: + * @strv: + * + * Free a strv and all associated memory. Works on packed + * or unpacked strv's. + **/ +void +e_strv_destroy(struct _EStrv *strv) +{ + struct _e_strvunpacked *s; + int i; + + s(printf("freeing strv\n")); + + if (strv->length == STRV_UNPACKED) { + s = (struct _e_strvunpacked *)strv; + if (s->pool) + e_mempool_destroy(s->pool); + if (s->source) + e_strv_destroy(s->source); + for (i=0;i<s->length;i++) { + if (s->strings[i].free) + g_free(s->strings[i].free); + } + } + + s(printf("freeing strv=%p\n", strv)); + + g_free(strv); +} + + + +/* string pool stuff */ + +/* TODO: + garbage collection, using the following technique: + Create a memchunk for each possible size of poolv, and allocate every poolv from those + To garbage collect, scan all memchunk internally, ignoring any free areas (or mark each + poolv when freeing it - set length 0?), and find out which strings are not anywhere, + then free them. + + OR: + Just keep a refcount in the hashtable, instead of duplicating the key pointer. + + either would also require a free for the mempool, so ignore it for now */ + +/*#define POOLV_REFCNT*/ /* Define to enable refcounting code that does + automatic garbage collection of unused strings */ + +static GHashTable *poolv_pool = NULL; +static EMemPool *poolv_mempool = NULL; + +#ifdef MALLOC_CHECK +static GPtrArray *poolv_table = NULL; +#endif + +#ifdef PROFILE_POOLV +static gulong poolv_hits = 0; +static gulong poolv_misses = 0; +static unsigned long poolv_mem, poolv_count; +#endif + +#ifdef G_THREADS_ENABLED +static GStaticMutex poolv_mutex = G_STATIC_MUTEX_INIT; +#endif + +struct _EPoolv { + unsigned char length; + char *s[1]; +}; + +/** + * e_poolv_new: @size: The number of elements in the poolv, maximum of 254 elements. + * + * create a new poolv (string vector which shares a global string + * pool). poolv's can be used to work with arrays of strings which + * save memory by eliminating duplicated allocations of the same + * string. + * + * this is useful when you have a log of read-only strings that do not + * go away and are duplicated a lot (such as email headers). + * + * we should probably in the future ref count the strings contained in + * the hash table, but for now let's not. + * + * Return value: new pooled string vector + **/ +EPoolv * +e_poolv_new(unsigned int size) +{ + EPoolv *poolv; + + g_assert(size < 255); + + poolv = g_malloc0(sizeof (*poolv) + (size - 1) * sizeof (char *)); + poolv->length = size; + +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&poolv_mutex); +#endif + if (!poolv_pool) + poolv_pool = g_hash_table_new(g_str_hash, g_str_equal); + + if (!poolv_mempool) + poolv_mempool = e_mempool_new(32 * 1024, 512, E_MEMPOOL_ALIGN_BYTE); + +#ifdef MALLOC_CHECK + { + int i; + + if (poolv_table == NULL) + poolv_table = g_ptr_array_new(); + + for (i=0;i<poolv_table->len;i++) + MPROBE(poolv_table->pdata[i]); + + g_ptr_array_add(poolv_table, poolv); + } +#endif + +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&poolv_mutex); +#endif + + p(printf("new poolv=%p\tsize=%d\n", poolv, sizeof(*poolv) + (size-1)*sizeof(char *))); + +#ifdef PROFILE_POOLV + poolv_count++; +#endif + return poolv; +} + +/** + * e_poolv_cpy: + * @dest: destination pooled string vector + * @src: source pooled string vector + * + * Copy the contents of a pooled string vector + * + * Return value: @dest, which may be re-allocated if the strings + * are different lengths. + **/ +EPoolv * +e_poolv_cpy(EPoolv *dest, const EPoolv *src) +{ +#ifdef POOLV_REFCNT + int i; + unsigned int ref; + char *key; +#endif + + p2(g_return_val_if_fail (dest != NULL, NULL)); + p2(g_return_val_if_fail (src != NULL, NULL)); + + MPROBE(dest); + MPROBE(src); + + if (dest->length != src->length) { + e_poolv_destroy(dest); + dest = e_poolv_new(src->length); + } + +#ifdef POOLV_REFCNT +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&poolv_mutex); +#endif + /* ref new copies */ + for (i=0;i<src->length;i++) { + if (src->s[i]) { + if (g_hash_table_lookup_extended(poolv_pool, src->s[i], (void **)&key, (void **)&ref)) { + g_hash_table_insert(poolv_pool, key, (void *)(ref+1)); + } else { + g_assert_not_reached(); + } + } + } + + /* unref the old ones */ + for (i=0;i<dest->length;i++) { + if (dest->s[i]) { + if (g_hash_table_lookup_extended(poolv_pool, dest->s[i], (void **)&key, (void **)&ref)) { + /* if ref == 1 free it */ + g_assert(ref > 0); + g_hash_table_insert(poolv_pool, key, (void *)(ref-1)); + } else { + g_assert_not_reached(); + } + } + } +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&poolv_mutex); +#endif +#endif + + memcpy(dest->s, src->s, src->length * sizeof (char *)); + + return dest; +} + +#ifdef PROFILE_POOLV +static void +poolv_profile_update (void) +{ + static time_t last_time = 0; + time_t new_time; + + new_time = time (NULL); + if (new_time - last_time < 5) + return; + + printf("poolv profile: %lu hits, %lu misses: %d%% hit rate, memory: %lu, instances: %lu\n", + poolv_hits, poolv_misses, + (int)(100.0 * ((double) poolv_hits / (double) (poolv_hits + poolv_misses))), + poolv_mem, poolv_count); + + last_time = new_time; +} +#endif + +/** + * e_poolv_set: + * @poolv: pooled string vector + * @index: index in vector of string + * @str: string to set + * @freeit: whether the caller is releasing its reference to the + * string + * + * Set a string vector reference. If the caller will no longer be + * referencing the string, freeit should be TRUE. Otherwise, this + * will duplicate the string if it is not found in the pool. + * + * Return value: @poolv + **/ +EPoolv * +e_poolv_set (EPoolv *poolv, int index, char *str, int freeit) +{ +#ifdef POOLV_REFCNT + unsigned int ref; + char *key; +#endif + + p2(g_return_val_if_fail (poolv != NULL, NULL)); + + g_assert(index >=0 && index < poolv->length); + + MPROBE(poolv); + + p(printf("setting %d `%s'\n", index, str)); + + if (!str) { +#ifdef POOLV_REFCNT + if (poolv->s[index]) { + if (g_hash_table_lookup_extended(poolv_pool, poolv->s[index], (void **)&key, (void **)&ref)) { + g_assert(ref > 0); + g_hash_table_insert(poolv_pool, key, (void *)(ref-1)); + } else { + g_assert_not_reached(); + } + } +#endif + poolv->s[index] = NULL; + return poolv; + } + +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&poolv_mutex); +#endif + +#ifdef POOLV_REFCNT + if (g_hash_table_lookup_extended(poolv_pool, str, (void **)&key, (void **)&ref)) { + g_hash_table_insert(poolv_pool, key, (void *)(ref+1)); + poolv->s[index] = key; +# ifdef PROFILE_POOLV + poolv_hits++; + poolv_profile_update (); +# endif + } else { +# ifdef PROFILE_POOLV + poolv_misses++; + poolv_mem += strlen(str); + poolv_profile_update (); +# endif + poolv->s[index] = e_mempool_strdup(poolv_mempool, str); + g_hash_table_insert(poolv_pool, poolv->s[index], (void *)1); + } + +#else /* !POOLV_REFCNT */ + if ((poolv->s[index] = g_hash_table_lookup(poolv_pool, str)) != NULL) { +# ifdef PROFILE_POOLV + poolv_hits++; + poolv_profile_update (); +# endif + } else { +# ifdef PROFILE_POOLV + poolv_misses++; + poolv_mem += strlen(str); + poolv_profile_update (); +# endif + poolv->s[index] = e_mempool_strdup(poolv_mempool, str); + g_hash_table_insert(poolv_pool, poolv->s[index], poolv->s[index]); + } +#endif /* !POOLV_REFCNT */ + +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&poolv_mutex); +#endif + + if (freeit) + g_free(str); + + return poolv; +} + +/** + * e_poolv_get: + * @poolv: pooled string vector + * @index: index in vector of string + * + * Retrieve a string by index. This could possibly just be a macro. + * + * Since the pool is never freed, this string does not need to be + * duplicated, but should not be modified. + * + * Return value: string at that index. + **/ +const char * +e_poolv_get(EPoolv *poolv, int index) +{ + g_assert(poolv != NULL); + g_assert(index>= 0 && index < poolv->length); + + MPROBE(poolv); + + p(printf("get %d = `%s'\n", index, poolv->s[index])); + + return poolv->s[index]?poolv->s[index]:""; +} + +/** + * e_poolv_destroy: + * @poolv: pooled string vector to free + * + * Free a pooled string vector. This doesn't free the strings from + * the vector, however. + **/ +void +e_poolv_destroy(EPoolv *poolv) +{ +#ifdef POOLV_REFCNT + int i; + unsigned int ref; + char *key; + + MPROBE(poolv); + +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&poolv_mutex); +#endif + +#ifdef MALLOC_CHECK + for (i=0;i<poolv_table->len;i++) + MPROBE(poolv_table->pdata[i]); + + g_ptr_array_remove_fast(poolv_table, poolv); +#endif + + for (i=0;i<poolv->length;i++) { + if (poolv->s[i]) { + if (g_hash_table_lookup_extended(poolv_pool, poolv->s[i], (void **)&key, (void **)&ref)) { + /* if ref == 1 free it */ + g_assert(ref > 0); + g_hash_table_insert(poolv_pool, key, (void *)(ref-1)); + } else { + g_assert_not_reached(); + } + } + } +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&poolv_mutex); +#endif +#endif + +#ifdef PROFILE_POOLV + poolv_count++; +#endif + g_free(poolv); +} + +#if 0 + +#define CHUNK_SIZE (20) +#define CHUNK_COUNT (32) + +#define s(x) + +main() +{ + int i; + MemChunk *mc; + void *mem, *last; + GMemChunk *gmc; + struct _EStrv *s; + + s = strv_new(8); + s = strv_set(s, 1, "Testing 1"); + s = strv_set(s, 2, "Testing 2"); + s = strv_set(s, 3, "Testing 3"); + s = strv_set(s, 4, "Testing 4"); + s = strv_set(s, 5, "Testing 5"); + s = strv_set(s, 6, "Testing 7"); + + for (i=0;i<8;i++) { + printf("s[%d] = %s\n", i, strv_get(s, i)); + } + + s(sleep(5)); + + printf("packing ...\n"); + s = strv_pack(s); + + for (i=0;i<8;i++) { + printf("s[%d] = %s\n", i, strv_get(s, i)); + } + + printf("setting ...\n"); + + s = strv_set_ref(s, 1, "Testing 1 x"); + + for (i=0;i<8;i++) { + printf("s[%d] = %s\n", i, strv_get(s, i)); + } + + printf("packing ...\n"); + s = strv_pack(s); + + for (i=0;i<8;i++) { + printf("s[%d] = %s\n", i, strv_get(s, i)); + } + + strv_free(s); + +#if 0 + time_start("Using memchunks"); + mc = memchunk_new(CHUNK_COUNT, CHUNK_SIZE); + for (i=0;i<1000000;i++) { + mem = memchunk_alloc(mc); + if ((i & 1) == 0) + memchunk_free(mc, mem); + } + s(sleep(10)); + memchunk_destroy(mc); + time_end("allocating 1000000 memchunks, freeing 500k"); + + time_start("Using gmemchunks"); + gmc = g_mem_chunk_new("memchunk", CHUNK_SIZE, CHUNK_SIZE*CHUNK_COUNT, G_ALLOC_AND_FREE); + for (i=0;i<1000000;i++) { + mem = g_mem_chunk_alloc(gmc); + if ((i & 1) == 0) + g_mem_chunk_free(gmc, mem); + } + s(sleep(10)); + g_mem_chunk_destroy(gmc); + time_end("allocating 1000000 gmemchunks, freeing 500k"); + + time_start("Using memchunks"); + mc = memchunk_new(CHUNK_COUNT, CHUNK_SIZE); + for (i=0;i<1000000;i++) { + mem = memchunk_alloc(mc); + } + s(sleep(10)); + memchunk_destroy(mc); + time_end("allocating 1000000 memchunks"); + + time_start("Using gmemchunks"); + gmc = g_mem_chunk_new("memchunk", CHUNK_SIZE, CHUNK_COUNT*CHUNK_SIZE, G_ALLOC_ONLY); + for (i=0;i<1000000;i++) { + mem = g_mem_chunk_alloc(gmc); + } + s(sleep(10)); + g_mem_chunk_destroy(gmc); + time_end("allocating 1000000 gmemchunks"); + + time_start("Using malloc"); + for (i=0;i<1000000;i++) { + malloc(CHUNK_SIZE); + } + time_end("allocating 1000000 malloc"); +#endif + +} + +#endif diff --git a/libedataserver/e-memory.h b/libedataserver/e-memory.h new file mode 100644 index 000000000..9cc89f2f2 --- /dev/null +++ b/libedataserver/e-memory.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2001, Ximian Inc. + * + * Authors: Michael Zucchi <notzed@ximian.com> + * Jacob Berkman <jacob@ximian.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 + */ + +#ifndef _E_MEMORY_H +#define _E_MEMORY_H + +/* memchunks - allocate/free fixed-size blocks of memory */ +/* this is like gmemchunk, only faster and less overhead (only 4 bytes for every atomcount allocations) */ +typedef struct _EMemChunk EMemChunk; + +EMemChunk *e_memchunk_new(int atomcount, int atomsize); +void *e_memchunk_alloc(EMemChunk *m); +void *e_memchunk_alloc0(EMemChunk *m); +void e_memchunk_free(EMemChunk *m, void *mem); +void e_memchunk_empty(EMemChunk *m); +void e_memchunk_clean(EMemChunk *m); +void e_memchunk_destroy(EMemChunk *m); + +/* mempools - allocate variable sized blocks of memory, and free as one */ +/* allocation is very fast, but cannot be freed individually */ +typedef struct _EMemPool EMemPool; +typedef enum { + E_MEMPOOL_ALIGN_STRUCT = 0, /* allocate to native structure alignment */ + E_MEMPOOL_ALIGN_WORD = 1, /* allocate to words - 16 bit alignment */ + E_MEMPOOL_ALIGN_BYTE = 2, /* allocate to bytes - 8 bit alignment */ + E_MEMPOOL_ALIGN_MASK = 3, /* which bits determine the alignment information */ +} EMemPoolFlags; + +EMemPool *e_mempool_new(int blocksize, int threshold, EMemPoolFlags flags); +void *e_mempool_alloc(EMemPool *pool, int size); +char *e_mempool_strdup(EMemPool *pool, const char *str); +void e_mempool_flush(EMemPool *pool, int freeall); +void e_mempool_destroy(EMemPool *pool); + +/* strv's string arrays that can be efficiently modified and then compressed mainly for retrival */ +/* building is relatively fast, once compressed it takes the minimum amount of memory possible to store */ +typedef struct _EStrv EStrv; + +EStrv *e_strv_new(int size); +EStrv *e_strv_set_ref(EStrv *strv, int index, char *str); +EStrv *e_strv_set_ref_free(EStrv *strv, int index, char *str); +EStrv *e_strv_set(EStrv *strv, int index, const char *str); +EStrv *e_strv_pack(EStrv *strv); +char *e_strv_get(EStrv *strv, int index); +void e_strv_destroy(EStrv *strv); + +/* poolv's are similar to strv's, but they store common strings */ +typedef struct _EPoolv EPoolv; + +EPoolv *e_poolv_new(unsigned int size); +EPoolv *e_poolv_cpy(EPoolv *dest, const EPoolv *src); +EPoolv *e_poolv_set(EPoolv *poolv, int index, char *str, int freeit); +const char *e_poolv_get(EPoolv *poolv, int index); +void e_poolv_destroy(EPoolv *poolv); + +#endif /* ! _E_MEMORY_H */ diff --git a/libedataserver/e-msgport.c b/libedataserver/e-msgport.c new file mode 100644 index 000000000..36ea6cfe8 --- /dev/null +++ b/libedataserver/e-msgport.c @@ -0,0 +1,1041 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#include <pthread.h> + +#include <glib.h> + +#ifdef HAVE_NSS +#include <nspr.h> +#endif + +#include "e-msgport.h" + +#define m(x) /* msgport debug */ +#define t(x) /* thread debug */ + +void e_dlist_init(EDList *v) +{ + v->head = (EDListNode *)&v->tail; + v->tail = 0; + v->tailpred = (EDListNode *)&v->head; +} + +EDListNode *e_dlist_addhead(EDList *l, EDListNode *n) +{ + n->next = l->head; + n->prev = (EDListNode *)&l->head; + l->head->prev = n; + l->head = n; + return n; +} + +EDListNode *e_dlist_addtail(EDList *l, EDListNode *n) +{ + n->next = (EDListNode *)&l->tail; + n->prev = l->tailpred; + l->tailpred->next = n; + l->tailpred = n; + return n; +} + +EDListNode *e_dlist_remove(EDListNode *n) +{ + n->next->prev = n->prev; + n->prev->next = n->next; + return n; +} + +EDListNode *e_dlist_remhead(EDList *l) +{ + EDListNode *n, *nn; + + n = l->head; + nn = n->next; + if (nn) { + nn->prev = n->prev; + l->head = nn; + return n; + } + return NULL; +} + +EDListNode *e_dlist_remtail(EDList *l) +{ + EDListNode *n, *np; + + n = l->tailpred; + np = n->prev; + if (np) { + np->next = n->next; + l->tailpred = np; + return n; + } + return NULL; +} + +int e_dlist_empty(EDList *l) +{ + return (l->head == (EDListNode *)&l->tail); +} + +int e_dlist_length(EDList *l) +{ + EDListNode *n, *nn; + int count = 0; + + n = l->head; + nn = n->next; + while (nn) { + count++; + n = nn; + nn = n->next; + } + + return count; +} + +struct _EMsgPort { + EDList queue; + int condwait; /* how many waiting in condwait */ + union { + int pipe[2]; + struct { + int read; + int write; + } fd; + } pipe; +#ifdef HAVE_NSS + struct { + PRFileDesc *read; + PRFileDesc *write; + } prpipe; +#endif + /* @#@$#$ glib stuff */ + GCond *cond; + GMutex *lock; +}; + +EMsgPort *e_msgport_new(void) +{ + EMsgPort *mp; + + mp = g_malloc(sizeof(*mp)); + e_dlist_init(&mp->queue); + mp->lock = g_mutex_new(); + mp->cond = g_cond_new(); + mp->pipe.fd.read = -1; + mp->pipe.fd.write = -1; +#ifdef HAVE_NSS + mp->prpipe.read = NULL; + mp->prpipe.write = NULL; +#endif + mp->condwait = 0; + + return mp; +} + +void e_msgport_destroy(EMsgPort *mp) +{ + g_mutex_free(mp->lock); + g_cond_free(mp->cond); + if (mp->pipe.fd.read != -1) { + close(mp->pipe.fd.read); + close(mp->pipe.fd.write); + } +#ifdef HAVE_NSS + if (mp->prpipe.read) { + PR_Close(mp->prpipe.read); + PR_Close(mp->prpipe.write); + } +#endif + g_free(mp); +} + +/* get a fd that can be used to wait on the port asynchronously */ +int e_msgport_fd(EMsgPort *mp) +{ + int fd; + + g_mutex_lock(mp->lock); + fd = mp->pipe.fd.read; + if (fd == -1) { + pipe(mp->pipe.pipe); + fd = mp->pipe.fd.read; + } + g_mutex_unlock(mp->lock); + + return fd; +} + +#ifdef HAVE_NSS +PRFileDesc *e_msgport_prfd(EMsgPort *mp) +{ + PRFileDesc *fd; + + g_mutex_lock(mp->lock); + fd = mp->prpipe.read; + if (fd == NULL) { + PR_CreatePipe(&mp->prpipe.read, &mp->prpipe.write); + fd = mp->prpipe.read; + } + g_mutex_unlock(mp->lock); + + return fd; +} +#endif + +void e_msgport_put(EMsgPort *mp, EMsg *msg) +{ + int fd; +#ifdef HAVE_NSS + PRFileDesc *prfd; +#endif + + m(printf("put:\n")); + g_mutex_lock(mp->lock); + e_dlist_addtail(&mp->queue, &msg->ln); + if (mp->condwait > 0) { + m(printf("put: condwait > 0, waking up\n")); + g_cond_signal(mp->cond); + } + fd = mp->pipe.fd.write; +#ifdef HAVE_NSS + prfd = mp->prpipe.write; +#endif + g_mutex_unlock(mp->lock); + + if (fd != -1) { + m(printf("put: have pipe, writing notification to it\n")); + write(fd, "", 1); + } + +#ifdef HAVE_NSS + if (prfd != NULL) { + m(printf("put: have pr pipe, writing notification to it\n")); + PR_Write(prfd, "", 1); + } +#endif + m(printf("put: done\n")); +} + +static void +msgport_cleanlock(void *data) +{ + EMsgPort *mp = data; + + g_mutex_unlock(mp->lock); +} + +EMsg *e_msgport_wait(EMsgPort *mp) +{ + EMsg *msg; + + m(printf("wait:\n")); + g_mutex_lock(mp->lock); + while (e_dlist_empty(&mp->queue)) { + if (mp->pipe.fd.read != -1) { + fd_set rfds; + int retry; + + m(printf("wait: waitng on pipe\n")); + g_mutex_unlock(mp->lock); + do { + FD_ZERO(&rfds); + FD_SET(mp->pipe.fd.read, &rfds); + retry = select(mp->pipe.fd.read+1, &rfds, NULL, NULL, NULL) == -1 && errno == EINTR; + pthread_testcancel(); + } while (retry); + g_mutex_lock(mp->lock); + m(printf("wait: got pipe\n")); +#ifdef HAVE_NSS + } else if (mp->prpipe.read != NULL) { + PRPollDesc polltable[1]; + int retry; + + m(printf("wait: waitng on pr pipe\n")); + g_mutex_unlock(mp->lock); + do { + polltable[0].fd = mp->prpipe.read; + polltable[0].in_flags = PR_POLL_READ|PR_POLL_ERR; + retry = PR_Poll(polltable, 1, PR_INTERVAL_NO_TIMEOUT) == -1 && PR_GetError() == PR_PENDING_INTERRUPT_ERROR; + pthread_testcancel(); + } while (retry); + g_mutex_lock(mp->lock); + m(printf("wait: got pr pipe\n")); +#endif /* HAVE_NSS */ + } else { + m(printf("wait: waiting on condition\n")); + mp->condwait++; + /* if we are cancelled in the cond-wait, then we need to unlock our lock when we cleanup */ + pthread_cleanup_push(msgport_cleanlock, mp); + g_cond_wait(mp->cond, mp->lock); + pthread_cleanup_pop(0); + m(printf("wait: got condition\n")); + mp->condwait--; + } + } + msg = (EMsg *)mp->queue.head; + m(printf("wait: message = %p\n", msg)); + g_mutex_unlock(mp->lock); + m(printf("wait: done\n")); + return msg; +} + +EMsg *e_msgport_get(EMsgPort *mp) +{ + EMsg *msg; + char dummy[1]; + + g_mutex_lock(mp->lock); + msg = (EMsg *)e_dlist_remhead(&mp->queue); + if (msg) { + if (mp->pipe.fd.read != -1) + read(mp->pipe.fd.read, dummy, 1); +#ifdef HAVE_NSS + if (mp->prpipe.read != NULL) { + int c; + c = PR_Read(mp->prpipe.read, dummy, 1); + g_assert(c == 1); + } +#endif + } + m(printf("get: message = %p\n", msg)); + g_mutex_unlock(mp->lock); + + return msg; +} + +void e_msgport_reply(EMsg *msg) +{ + if (msg->reply_port) { + e_msgport_put(msg->reply_port, msg); + } + /* else lost? */ +} + +struct _thread_info { + pthread_t id; + int busy; +}; + +struct _EThread { + struct _EThread *next; + struct _EThread *prev; + + EMsgPort *server_port; + EMsgPort *reply_port; + pthread_mutex_t mutex; + e_thread_t type; + int queue_limit; + + int waiting; /* if we are waiting for a new message, count of waiting processes */ + pthread_t id; /* id of our running child thread */ + GList *id_list; /* if THREAD_NEW, then a list of our child threads in thread_info structs */ + + EThreadFunc destroy; + void *destroy_data; + + EThreadFunc received; + void *received_data; + + EThreadFunc lost; + void *lost_data; +}; + +/* All active threads */ +static EDList ethread_list = E_DLIST_INITIALISER(ethread_list); +static pthread_mutex_t ethread_lock = PTHREAD_MUTEX_INITIALIZER; + +#define E_THREAD_NONE ((pthread_t)~0) +#define E_THREAD_QUIT_REPLYPORT ((struct _EMsgPort *)~0) + +static void thread_destroy_msg(EThread *e, EMsg *m); + +static struct _thread_info *thread_find(EThread *e, pthread_t id) +{ + GList *node; + struct _thread_info *info; + + node = e->id_list; + while (node) { + info = node->data; + if (info->id == id) + return info; + node = node->next; + } + return NULL; +} + +#if 0 +static void thread_remove(EThread *e, pthread_t id) +{ + GList *node; + struct _thread_info *info; + + node = e->id_list; + while (node) { + info = node->data; + if (info->id == id) { + e->id_list = g_list_remove(e->id_list, info); + g_free(info); + } + node = node->next; + } +} +#endif + +EThread *e_thread_new(e_thread_t type) +{ + EThread *e; + + e = g_malloc0(sizeof(*e)); + pthread_mutex_init(&e->mutex, 0); + e->type = type; + e->server_port = e_msgport_new(); + e->id = E_THREAD_NONE; + e->queue_limit = INT_MAX; + + pthread_mutex_lock(ðread_lock); + e_dlist_addtail(ðread_list, (EDListNode *)e); + pthread_mutex_unlock(ðread_lock); + + return e; +} + +/* close down the threads & resources etc */ +void e_thread_destroy(EThread *e) +{ + int busy = FALSE; + EMsg *msg; + struct _thread_info *info; + GList *l; + + /* make sure we soak up all the messages first */ + while ( (msg = e_msgport_get(e->server_port)) ) { + thread_destroy_msg(e, msg); + } + + pthread_mutex_lock(&e->mutex); + + switch(e->type) { + case E_THREAD_QUEUE: + case E_THREAD_DROP: + /* if we have a thread, 'kill' it */ + if (e->id != E_THREAD_NONE) { + pthread_t id = e->id; + + t(printf("Sending thread '%d' quit message\n", id)); + + e->id = E_THREAD_NONE; + + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = E_THREAD_QUIT_REPLYPORT; + e_msgport_put(e->server_port, msg); + + pthread_mutex_unlock(&e->mutex); + t(printf("Joining thread '%d'\n", id)); + pthread_join(id, 0); + t(printf("Joined thread '%d'!\n", id)); + pthread_mutex_lock(&e->mutex); + } + busy = e->id != E_THREAD_NONE; + break; + case E_THREAD_NEW: + /* first, send everyone a quit message */ + l = e->id_list; + while (l) { + info = l->data; + t(printf("Sending thread '%d' quit message\n", info->id)); + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = E_THREAD_QUIT_REPLYPORT; + e_msgport_put(e->server_port, msg); + l = l->next; + } + + /* then, wait for everyone to quit */ + while (e->id_list) { + info = e->id_list->data; + e->id_list = g_list_remove(e->id_list, info); + pthread_mutex_unlock(&e->mutex); + t(printf("Joining thread '%d'\n", info->id)); + pthread_join(info->id, 0); + t(printf("Joined thread '%d'!\n", info->id)); + pthread_mutex_lock(&e->mutex); + g_free(info); + } + busy = g_list_length(e->id_list) != 0; + break; + } + + pthread_mutex_unlock(&e->mutex); + + /* and clean up, if we can */ + if (busy) { + g_warning("threads were busy, leaked EThread"); + return; + } + + pthread_mutex_lock(ðread_lock); + e_dlist_remove((EDListNode *)e); + pthread_mutex_unlock(ðread_lock); + + pthread_mutex_destroy(&e->mutex); + e_msgport_destroy(e->server_port); + g_free(e); +} + +/* set the queue maximum depth, what happens when the queue + fills up depends on the queue type */ +void e_thread_set_queue_limit(EThread *e, int limit) +{ + e->queue_limit = limit; +} + +/* set a msg destroy callback, this can not call any e_thread functions on @e */ +void e_thread_set_msg_destroy(EThread *e, EThreadFunc destroy, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->destroy = destroy; + e->destroy_data = data; + pthread_mutex_unlock(&e->mutex); +} + +/* set a message lost callback, called if any message is discarded */ +void e_thread_set_msg_lost(EThread *e, EThreadFunc lost, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->lost = lost; + e->lost_data = lost; + pthread_mutex_unlock(&e->mutex); +} + +/* set a reply port, if set, then send messages back once finished */ +void e_thread_set_reply_port(EThread *e, EMsgPort *reply_port) +{ + e->reply_port = reply_port; +} + +/* set a received data callback */ +void e_thread_set_msg_received(EThread *e, EThreadFunc received, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->received = received; + e->received_data = data; + pthread_mutex_unlock(&e->mutex); +} + +/* find out if we're busy doing any work, e==NULL, check for all work */ +int e_thread_busy(EThread *e) +{ + int busy = FALSE; + + if (e == NULL) { + pthread_mutex_lock(ðread_lock); + e = (EThread *)ethread_list.head; + while (e->next && !busy) { + busy = e_thread_busy(e); + e = e->next; + } + pthread_mutex_unlock(ðread_lock); + } else { + pthread_mutex_lock(&e->mutex); + switch (e->type) { + case E_THREAD_QUEUE: + case E_THREAD_DROP: + busy = e->waiting != 1 && e->id != E_THREAD_NONE; + break; + case E_THREAD_NEW: + busy = e->waiting != g_list_length(e->id_list); + break; + } + pthread_mutex_unlock(&e->mutex); + } + + return busy; +} + +static void +thread_destroy_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->destroy; + func_data = e->destroy_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); +} + +static void +thread_received_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->received; + func_data = e->received_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); + else + g_warning("No processing callback for EThread, message unprocessed"); +} + +static void +thread_lost_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->lost; + func_data = e->lost_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); +} + +/* the actual thread dispatcher */ +static void * +thread_dispatch(void *din) +{ + EThread *e = din; + EMsg *m; + struct _thread_info *info; + pthread_t self = pthread_self(); + + t(printf("dispatch thread started: %ld\n", pthread_self())); + + while (1) { + pthread_mutex_lock(&e->mutex); + m = e_msgport_get(e->server_port); + if (m == NULL) { + /* nothing to do? If we are a 'new' type thread, just quit. + Otherwise, go into waiting (can be cancelled here) */ + info = NULL; + switch (e->type) { + case E_THREAD_NEW: + case E_THREAD_QUEUE: + case E_THREAD_DROP: + info = thread_find(e, self); + if (info) + info->busy = FALSE; + e->waiting++; + pthread_mutex_unlock(&e->mutex); + e_msgport_wait(e->server_port); + pthread_mutex_lock(&e->mutex); + e->waiting--; + pthread_mutex_unlock(&e->mutex); + break; +#if 0 + case E_THREAD_NEW: + e->id_list = g_list_remove(e->id_list, (void *)pthread_self()); + pthread_mutex_unlock(&e->mutex); + return 0; +#endif + } + + continue; + } else if (m->reply_port == E_THREAD_QUIT_REPLYPORT) { + t(printf("Thread %d got quit message\n", self)); + /* Handle a quit message, say we're quitting, free the message, and break out of the loop */ + info = thread_find(e, self); + if (info) + info->busy = 2; + pthread_mutex_unlock(&e->mutex); + g_free(m); + break; + } else { + info = thread_find(e, self); + if (info) + info->busy = TRUE; + } + pthread_mutex_unlock(&e->mutex); + + t(printf("got message in dispatch thread\n")); + + /* process it */ + thread_received_msg(e, m); + + /* if we have a reply port, send it back, otherwise, lose it */ + if (m->reply_port) { + e_msgport_reply(m); + } else { + thread_destroy_msg(e, m); + } + } + + return NULL; +} + +/* send a message to the thread, start thread if necessary */ +void e_thread_put(EThread *e, EMsg *msg) +{ + pthread_t id; + EMsg *dmsg = NULL; + + pthread_mutex_lock(&e->mutex); + + /* the caller forgot to tell us what to do, well, we can't do anything can we */ + if (e->received == NULL) { + pthread_mutex_unlock(&e->mutex); + g_warning("EThread called with no receiver function, no work to do!"); + thread_destroy_msg(e, msg); + return; + } + + msg->reply_port = e->reply_port; + + switch(e->type) { + case E_THREAD_QUEUE: + /* if the queue is full, lose this new addition */ + if (e_dlist_length(&e->server_port->queue) < e->queue_limit) { + e_msgport_put(e->server_port, msg); + } else { + printf("queue limit reached, dropping new message\n"); + dmsg = msg; + } + break; + case E_THREAD_DROP: + /* if the queue is full, lose the oldest (unprocessed) message */ + if (e_dlist_length(&e->server_port->queue) < e->queue_limit) { + e_msgport_put(e->server_port, msg); + } else { + printf("queue limit reached, dropping old message\n"); + e_msgport_put(e->server_port, msg); + dmsg = e_msgport_get(e->server_port); + } + break; + case E_THREAD_NEW: + /* it is possible that an existing thread can catch this message, so + we might create a thread with no work to do. + but that doesn't matter, the other alternative that it be lost is worse */ + e_msgport_put(e->server_port, msg); + if (e->waiting == 0 + && g_list_length(e->id_list) < e->queue_limit + && pthread_create(&id, NULL, thread_dispatch, e) == 0) { + struct _thread_info *info = g_malloc0(sizeof(*info)); + t(printf("created NEW thread %ld\n", id)); + info->id = id; + info->busy = TRUE; + e->id_list = g_list_append(e->id_list, info); + } + pthread_mutex_unlock(&e->mutex); + return; + } + + /* create the thread, if there is none to receive it yet */ + if (e->id == E_THREAD_NONE) { + int err; + + if ((err = pthread_create(&e->id, NULL, thread_dispatch, e)) != 0) { + g_warning("Could not create dispatcher thread, message queued?: %s", strerror(err)); + e->id = E_THREAD_NONE; + } + } + + pthread_mutex_unlock(&e->mutex); + + if (dmsg) { + thread_lost_msg(e, dmsg); + thread_destroy_msg(e, dmsg); + } +} + +/* yet-another-mutex interface */ +struct _EMutex { + int type; + pthread_t owner; + short waiters; + short depth; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +/* sigh, this is just painful to have to need, but recursive + read/write, etc mutexes just aren't very common in thread + implementations */ +/* TODO: Just make it use recursive mutexes if they are available */ +EMutex *e_mutex_new(e_mutex_t type) +{ + struct _EMutex *m; + + m = g_malloc(sizeof(*m)); + m->type = type; + m->waiters = 0; + m->depth = 0; + m->owner = E_THREAD_NONE; + + switch (type) { + case E_MUTEX_SIMPLE: + pthread_mutex_init(&m->mutex, 0); + break; + case E_MUTEX_REC: + pthread_mutex_init(&m->mutex, 0); + pthread_cond_init(&m->cond, 0); + break; + /* read / write ? flags for same? */ + } + + return m; +} + +int e_mutex_destroy(EMutex *m) +{ + int ret = 0; + + switch (m->type) { + case E_MUTEX_SIMPLE: + ret = pthread_mutex_destroy(&m->mutex); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + g_free(m); + break; + case E_MUTEX_REC: + ret = pthread_mutex_destroy(&m->mutex); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + ret = pthread_cond_destroy(&m->cond); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + g_free(m); + + } + return ret; +} + +int e_mutex_lock(EMutex *m) +{ + pthread_t id; + int err; + + switch (m->type) { + case E_MUTEX_SIMPLE: + return pthread_mutex_lock(&m->mutex); + case E_MUTEX_REC: + id = pthread_self(); + if ((err = pthread_mutex_lock(&m->mutex)) != 0) + return err; + while (1) { + if (m->owner == E_THREAD_NONE) { + m->owner = id; + m->depth = 1; + break; + } else if (id == m->owner) { + m->depth++; + break; + } else { + m->waiters++; + if ((err = pthread_cond_wait(&m->cond, &m->mutex)) != 0) + return err; + m->waiters--; + } + } + return pthread_mutex_unlock(&m->mutex); + } + + return EINVAL; +} + +int e_mutex_unlock(EMutex *m) +{ + int err; + + switch (m->type) { + case E_MUTEX_SIMPLE: + return pthread_mutex_unlock(&m->mutex); + case E_MUTEX_REC: + if ((err = pthread_mutex_lock(&m->mutex)) != 0) + return err; + g_assert(m->owner == pthread_self()); + + m->depth--; + if (m->depth == 0) { + m->owner = E_THREAD_NONE; + if (m->waiters > 0) + pthread_cond_signal(&m->cond); + } + return pthread_mutex_unlock(&m->mutex); + } + + errno = EINVAL; + return -1; +} + +void e_mutex_assert_locked(EMutex *m) +{ + g_return_if_fail (m->type == E_MUTEX_REC); + pthread_mutex_lock(&m->mutex); + g_assert(m->owner == pthread_self()); + pthread_mutex_unlock(&m->mutex); +} + +int e_mutex_cond_wait(void *vcond, EMutex *m) +{ + int ret; + pthread_cond_t *cond = vcond; + + switch(m->type) { + case E_MUTEX_SIMPLE: + return pthread_cond_wait(cond, &m->mutex); + case E_MUTEX_REC: + if ((ret = pthread_mutex_lock(&m->mutex)) != 0) + return ret; + g_assert(m->owner == pthread_self()); + ret = pthread_cond_wait(cond, &m->mutex); + g_assert(m->owner == pthread_self()); + pthread_mutex_unlock(&m->mutex); + return ret; + default: + g_return_val_if_reached(-1); + } +} + +#ifdef STANDALONE +EMsgPort *server_port; + + +void *fdserver(void *data) +{ + int fd; + EMsg *msg; + int id = (int)data; + fd_set rfds; + + fd = e_msgport_fd(server_port); + + while (1) { + int count = 0; + + printf("server %d: waiting on fd %d\n", id, fd); + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + select(fd+1, &rfds, NULL, NULL, NULL); + printf("server %d: Got async notification, checking for messages\n", id); + while ((msg = e_msgport_get(server_port))) { + printf("server %d: got message\n", id); + sleep(1); + printf("server %d: replying\n", id); + e_msgport_reply(msg); + count++; + } + printf("server %d: got %d messages\n", id, count); + } +} + +void *server(void *data) +{ + EMsg *msg; + int id = (int)data; + + while (1) { + printf("server %d: waiting\n", id); + msg = e_msgport_wait(server_port); + msg = e_msgport_get(server_port); + if (msg) { + printf("server %d: got message\n", id); + sleep(1); + printf("server %d: replying\n", id); + e_msgport_reply(msg); + } else { + printf("server %d: didn't get message\n", id); + } + } +} + +void *client(void *data) +{ + EMsg *msg; + EMsgPort *replyport; + int i; + + replyport = e_msgport_new(); + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = replyport; + for (i=0;i<10;i++) { + /* synchronous operation */ + printf("client: sending\n"); + e_msgport_put(server_port, msg); + printf("client: waiting for reply\n"); + e_msgport_wait(replyport); + e_msgport_get(replyport); + printf("client: got reply\n"); + } + + printf("client: sleeping ...\n"); + sleep(2); + printf("client: sending multiple\n"); + + for (i=0;i<10;i++) { + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = replyport; + e_msgport_put(server_port, msg); + } + + printf("client: receiving multiple\n"); + for (i=0;i<10;i++) { + e_msgport_wait(replyport); + msg = e_msgport_get(replyport); + g_free(msg); + } + + printf("client: done\n"); +} + +int main(int argc, char **argv) +{ + pthread_t serverid, clientid; + + g_thread_init(NULL); + + server_port = e_msgport_new(); + + /*pthread_create(&serverid, NULL, server, (void *)1);*/ + pthread_create(&serverid, NULL, fdserver, (void *)1); + pthread_create(&clientid, NULL, client, NULL); + + sleep(60); + + return 0; +} +#endif diff --git a/libedataserver/e-msgport.h b/libedataserver/e-msgport.h new file mode 100644 index 000000000..8d4e0c20f --- /dev/null +++ b/libedataserver/e-msgport.h @@ -0,0 +1,88 @@ + +#ifndef _E_MSGPORT_H +#define _E_MSGPORT_H + +/* double-linked list yeah another one, deal */ +typedef struct _EDListNode { + struct _EDListNode *next; + struct _EDListNode *prev; +} EDListNode; + +typedef struct _EDList { + struct _EDListNode *head; + struct _EDListNode *tail; + struct _EDListNode *tailpred; +} EDList; + +#define E_DLIST_INITIALISER(l) { (EDListNode *)&l.tail, 0, (EDListNode *)&l.head } + +void e_dlist_init(EDList *v); +EDListNode *e_dlist_addhead(EDList *l, EDListNode *n); +EDListNode *e_dlist_addtail(EDList *l, EDListNode *n); +EDListNode *e_dlist_remove(EDListNode *n); +EDListNode *e_dlist_remhead(EDList *l); +EDListNode *e_dlist_remtail(EDList *l); +int e_dlist_empty(EDList *l); +int e_dlist_length(EDList *l); + +/* message ports - a simple inter-thread 'ipc' primitive */ +/* opaque handle */ +typedef struct _EMsgPort EMsgPort; + +/* header for any message */ +typedef struct _EMsg { + EDListNode ln; + EMsgPort *reply_port; +} EMsg; + +EMsgPort *e_msgport_new(void); +void e_msgport_destroy(EMsgPort *mp); +/* get a fd that can be used to wait on the port asynchronously */ +int e_msgport_fd(EMsgPort *mp); +void e_msgport_put(EMsgPort *mp, EMsg *msg); +EMsg *e_msgport_wait(EMsgPort *mp); +EMsg *e_msgport_get(EMsgPort *mp); +void e_msgport_reply(EMsg *msg); +#ifdef HAVE_NSS +struct PRFileDesc *e_msgport_prfd(EMsgPort *mp); +#endif + +/* e threads, a server thread with a message based request-response, and flexible queuing */ +typedef struct _EThread EThread; + +typedef enum { + E_THREAD_QUEUE = 0, /* run one by one, until done, if the queue_limit is reached, discard new request */ + E_THREAD_DROP, /* run one by one, until done, if the queue_limit is reached, discard oldest requests */ + E_THREAD_NEW, /* always run in a new thread, if the queue limit is reached, new requests are + stored in the queue until a thread becomes available for it, creating a thread pool */ +} e_thread_t; + +typedef void (*EThreadFunc)(EThread *, EMsg *, void *data); + +EThread *e_thread_new(e_thread_t type); +void e_thread_destroy(EThread *e); +void e_thread_set_queue_limit(EThread *e, int limit); +void e_thread_set_msg_lost(EThread *e, EThreadFunc destroy, void *data); +void e_thread_set_msg_destroy(EThread *e, EThreadFunc destroy, void *data); +void e_thread_set_reply_port(EThread *e, EMsgPort *reply_port); +void e_thread_set_msg_received(EThread *e, EThreadFunc received, void *data); +void e_thread_put(EThread *e, EMsg *msg); +int e_thread_busy(EThread *e); + +/* sigh, another mutex interface, this one allows different mutex types, portably */ +typedef struct _EMutex EMutex; + +typedef enum _e_mutex_t { + E_MUTEX_SIMPLE, /* == pthread_mutex */ + E_MUTEX_REC, /* recursive mutex */ +} e_mutex_t; + +EMutex *e_mutex_new(e_mutex_t type); +int e_mutex_destroy(EMutex *m); +int e_mutex_lock(EMutex *m); +int e_mutex_unlock(EMutex *m); +void e_mutex_assert_locked(EMutex *m); +/* this uses pthread cond's */ +int e_mutex_cond_wait(void *cond, EMutex *m); + +#endif diff --git a/libedataserver/e-sexp.c b/libedataserver/e-sexp.c new file mode 100644 index 000000000..a7619c59f --- /dev/null +++ b/libedataserver/e-sexp.c @@ -0,0 +1,1379 @@ +/* + * Copyright 2000 Ximian (www.ximian.com). + * + * A simple, extensible s-exp evaluation engine. + * + * Author : + * Michael Zucchi <notzed@ximian.com> + + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 + */ + +/* + The following built-in s-exp's are supported: + + list = (and list*) + perform an intersection of a number of lists, and return that. + + bool = (and bool*) + perform a boolean AND of boolean values. + + list = (or list*) + perform a union of a number of lists, returning the new list. + + bool = (or bool*) + perform a boolean OR of boolean values. + + int = (+ int*) + Add integers. + + string = (+ string*) + Concat strings. + + time_t = (+ time_t*) + Add time_t values. + + int = (- int int*) + Subtract integers from the first. + + time_t = (- time_t*) + Subtract time_t values from the first. + + int = (cast-int string|int|bool) + Cast to an integer value. + + string = (cast-string string|int|bool) + Cast to an string value. + + Comparison operators: + + bool = (< int int) + bool = (> int int) + bool = (= int int) + + bool = (< string string) + bool = (> string string) + bool = (= string string) + + bool = (< time_t time_t) + bool = (> time_t time_t) + bool = (= time_t time_t) + Perform a comparision of 2 integers, 2 string values, or 2 time values. + + Function flow: + + type = (if bool function) + type = (if bool function function) + Choose a flow path based on a boolean value + + type = (begin func func func) + Execute a sequence. The last function return is the return type. +*/ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> + +#include "e-sexp.h" +#include "e-memory.h" + +#define p(x) /* parse debug */ +#define r(x) /* run debug */ +#define d(x) /* general debug */ + + +static struct _ESExpTerm * parse_list(ESExp *f, int gotbrace); +static struct _ESExpTerm * parse_value(ESExp *f); + +static void parse_dump_term(struct _ESExpTerm *t, int depth); + +#ifdef E_SEXP_IS_G_OBJECT +static GObjectClass *parent_class; +#endif + +static GScannerConfig scanner_config = +{ + ( " \t\r\n") /* cset_skip_characters */, + ( G_CSET_a_2_z + "_+-<=>?" + G_CSET_A_2_Z) /* cset_identifier_first */, + ( G_CSET_a_2_z + "_0123456789-<>?" + G_CSET_A_2_Z + G_CSET_LATINS + G_CSET_LATINC ) /* cset_identifier_nth */, + ( ";\n" ) /* cpair_comment_single */, + + FALSE /* case_sensitive */, + + TRUE /* skip_comment_multi */, + TRUE /* skip_comment_single */, + TRUE /* scan_comment_multi */, + TRUE /* scan_identifier */, + TRUE /* scan_identifier_1char */, + FALSE /* scan_identifier_NULL */, + TRUE /* scan_symbols */, + FALSE /* scan_binary */, + TRUE /* scan_octal */, + TRUE /* scan_float */, + TRUE /* scan_hex */, + FALSE /* scan_hex_dollar */, + TRUE /* scan_string_sq */, + TRUE /* scan_string_dq */, + TRUE /* numbers_2_int */, + FALSE /* int_2_float */, + FALSE /* identifier_2_string */, + TRUE /* char_2_token */, + FALSE /* symbol_2_token */, + FALSE /* scope_0_fallback */, +}; + +/* jumps back to the caller of f->failenv, only to be called from inside a callback */ +void +e_sexp_fatal_error(struct _ESExp *f, char *why, ...) +{ + va_list args; + + if (f->error) + g_free(f->error); + + va_start(args, why); + f->error = g_strdup_vprintf(why, args); + va_end(args); + + longjmp(f->failenv, 1); +} + +const char * +e_sexp_error(struct _ESExp *f) +{ + return f->error; +} + +struct _ESExpResult * +e_sexp_result_new(struct _ESExp *f, int type) +{ + struct _ESExpResult *r = e_memchunk_alloc0(f->result_chunks); + r->type = type; + return r; +} + +void +e_sexp_result_free(struct _ESExp *f, struct _ESExpResult *t) +{ + if (t == NULL) + return; + + switch(t->type) { + case ESEXP_RES_ARRAY_PTR: + g_ptr_array_free(t->value.ptrarray, TRUE); + break; + case ESEXP_RES_BOOL: + case ESEXP_RES_INT: + case ESEXP_RES_TIME: + break; + case ESEXP_RES_STRING: + g_free(t->value.string); + break; + case ESEXP_RES_UNDEFINED: + break; + default: + g_assert_not_reached(); + } + e_memchunk_free(f->result_chunks, t); +} + +/* used in normal functions if they have to abort, and free their arguments */ +void +e_sexp_resultv_free(struct _ESExp *f, int argc, struct _ESExpResult **argv) +{ + int i; + + for (i=0;i<argc;i++) { + e_sexp_result_free(f, argv[i]); + } +} + +/* implementations for the builtin functions */ + +/* can you tell, i dont like glib? */ +/* we can only itereate a hashtable from a called function */ +struct _glib_sux_donkeys { + int count; + GPtrArray *uids; +}; + +/* ok, store any values that are in all sets */ +static void +g_lib_sux_htand(char *key, int value, struct _glib_sux_donkeys *fuckup) +{ + if (value == fuckup->count) { + g_ptr_array_add(fuckup->uids, key); + } +} + +/* or, store all unique values */ +static void +g_lib_sux_htor(char *key, int value, struct _glib_sux_donkeys *fuckup) +{ + g_ptr_array_add(fuckup->uids, key); +} + +static ESExpResult * +term_eval_and(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r, *r1; + GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal); + struct _glib_sux_donkeys lambdafoo; + int type=-1; + int bool = TRUE; + int i; + + r(printf("( and\n")); + + r = e_sexp_result_new(f, ESEXP_RES_UNDEFINED); + + for (i=0;bool && i<argc;i++) { + r1 = e_sexp_term_eval(f, argv[i]); + if (type == -1) + type = r1->type; + if (type != r1->type) { + e_sexp_result_free(f, r); + e_sexp_result_free(f, r1); + g_hash_table_destroy(ht); + e_sexp_fatal_error(f, "Invalid types in AND"); + } else if (r1->type == ESEXP_RES_ARRAY_PTR) { + char **a1; + int l1, j; + + a1 = (char **)r1->value.ptrarray->pdata; + l1 = r1->value.ptrarray->len; + for (j=0;j<l1;j++) { + gpointer ptr; + int n; + ptr = g_hash_table_lookup(ht, a1[j]); + n = GPOINTER_TO_INT(ptr); + g_hash_table_insert(ht, a1[j], GINT_TO_POINTER(n+1)); + } + } else if (r1->type == ESEXP_RES_BOOL) { + bool = bool && r1->value.bool; + } + e_sexp_result_free(f, r1); + } + + if (type == ESEXP_RES_ARRAY_PTR) { + lambdafoo.count = argc; + lambdafoo.uids = g_ptr_array_new(); + g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htand, &lambdafoo); + r->type = ESEXP_RES_ARRAY_PTR; + r->value.ptrarray = lambdafoo.uids; + } else if (type == ESEXP_RES_BOOL) { + r->type = ESEXP_RES_BOOL; + r->value.bool = bool; + } + + g_hash_table_destroy(ht); + + return r; +} + +static ESExpResult * +term_eval_or(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r, *r1; + GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal); + struct _glib_sux_donkeys lambdafoo; + int type = -1; + int bool = FALSE; + int i; + + r(printf("(or \n")); + + r = e_sexp_result_new(f, ESEXP_RES_UNDEFINED); + + for (i=0;!bool && i<argc;i++) { + r1 = e_sexp_term_eval(f, argv[i]); + if (type == -1) + type = r1->type; + if (r1->type != type) { + e_sexp_result_free(f, r); + e_sexp_result_free(f, r1); + g_hash_table_destroy(ht); + e_sexp_fatal_error(f, "Invalid types in OR"); + } else if (r1->type == ESEXP_RES_ARRAY_PTR) { + char **a1; + int l1, j; + + a1 = (char **)r1->value.ptrarray->pdata; + l1 = r1->value.ptrarray->len; + for (j=0;j<l1;j++) { + g_hash_table_insert(ht, a1[j], (void *)1); + } + } else if (r1->type == ESEXP_RES_BOOL) { + bool |= r1->value.bool; + } + e_sexp_result_free(f, r1); + } + + if (type == ESEXP_RES_ARRAY_PTR) { + lambdafoo.count = argc; + lambdafoo.uids = g_ptr_array_new(); + g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htor, &lambdafoo); + r->type = ESEXP_RES_ARRAY_PTR; + r->value.ptrarray = lambdafoo.uids; + } else if (type == ESEXP_RES_BOOL) { + r->type = ESEXP_RES_BOOL; + r->value.bool = bool; + } + g_hash_table_destroy(ht); + + return r; +} + +static ESExpResult * +term_eval_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + int res = TRUE; + ESExpResult *r; + + if (argc>0) { + if (argv[0]->type == ESEXP_RES_BOOL + && argv[0]->value.bool) + res = FALSE; + } + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = res; + return r; +} + +/* this should support all arguments ...? */ +static ESExpResult * +term_eval_lt(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r, *r1, *r2; + + r = e_sexp_result_new(f, ESEXP_RES_UNDEFINED); + + if (argc == 2) { + r1 = e_sexp_term_eval(f, argv[0]); + r2 = e_sexp_term_eval(f, argv[1]); + if (r1->type != r2->type) { + e_sexp_result_free(f, r1); + e_sexp_result_free(f, r2); + e_sexp_result_free(f, r); + e_sexp_fatal_error(f, "Incompatible types in compare <"); + } else if (r1->type == ESEXP_RES_INT) { + r->type = ESEXP_RES_BOOL; + r->value.bool = r1->value.number < r2->value.number; + } else if (r1->type == ESEXP_RES_TIME) { + r->type = ESEXP_RES_BOOL; + r->value.bool = r1->value.time < r2->value.time; + } else if (r1->type == ESEXP_RES_STRING) { + r->type = ESEXP_RES_BOOL; + r->value.bool = strcmp(r1->value.string, r2->value.string) < 0; + } + e_sexp_result_free(f, r1); + e_sexp_result_free(f, r2); + } + return r; +} + +/* this should support all arguments ...? */ +static ESExpResult * +term_eval_gt(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r, *r1, *r2; + + r = e_sexp_result_new(f, ESEXP_RES_UNDEFINED); + + if (argc == 2) { + r1 = e_sexp_term_eval(f, argv[0]); + r2 = e_sexp_term_eval(f, argv[1]); + if (r1->type != r2->type) { + e_sexp_result_free(f, r1); + e_sexp_result_free(f, r2); + e_sexp_result_free(f, r); + e_sexp_fatal_error(f, "Incompatible types in compare >"); + } else if (r1->type == ESEXP_RES_INT) { + r->type = ESEXP_RES_BOOL; + r->value.bool = r1->value.number > r2->value.number; + } else if (r1->type == ESEXP_RES_TIME) { + r->type = ESEXP_RES_BOOL; + r->value.bool = r1->value.time > r2->value.time; + } else if (r1->type == ESEXP_RES_STRING) { + r->type = ESEXP_RES_BOOL; + r->value.bool = strcmp(r1->value.string, r2->value.string) > 0; + } + e_sexp_result_free(f, r1); + e_sexp_result_free(f, r2); + } + return r; +} + +/* this should support all arguments ...? */ +static ESExpResult * +term_eval_eq(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r, *r1, *r2; + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + + if (argc == 2) { + r1 = e_sexp_term_eval(f, argv[0]); + r2 = e_sexp_term_eval(f, argv[1]); + if (r1->type != r2->type) { + r->value.bool = FALSE; + } else if (r1->type == ESEXP_RES_INT) { + r->value.bool = r1->value.number == r2->value.number; + } else if (r1->type == ESEXP_RES_BOOL) { + r->value.bool = r1->value.bool == r2->value.bool; + } else if (r1->type == ESEXP_RES_TIME) { + r->value.bool = r1->value.time == r2->value.time; + } else if (r1->type == ESEXP_RES_STRING) { + r->value.bool = strcmp(r1->value.string, r2->value.string) == 0; + } + e_sexp_result_free(f, r1); + e_sexp_result_free(f, r2); + } + return r; +} + +static ESExpResult * +term_eval_plus(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + struct _ESExpResult *r=NULL; + int type; + int i; + + if (argc>0) { + type = argv[0]->type; + switch(type) { + case ESEXP_RES_INT: { + int total = argv[0]->value.number; + for (i=1;i<argc && argv[i]->type == ESEXP_RES_INT;i++) { + total += argv[i]->value.number; + } + if (i<argc) { + e_sexp_resultv_free(f, argc, argv); + e_sexp_fatal_error(f, "Invalid types in (+ ints)"); + } + r = e_sexp_result_new(f, ESEXP_RES_INT); + r->value.number = total; + break; } + case ESEXP_RES_STRING: { + GString *s = g_string_new(argv[0]->value.string); + for (i=1;i<argc && argv[i]->type == ESEXP_RES_STRING;i++) { + g_string_append(s, argv[i]->value.string); + } + if (i<argc) { + e_sexp_resultv_free(f, argc, argv); + e_sexp_fatal_error(f, "Invalid types in (+ strings)"); + } + r = e_sexp_result_new(f, ESEXP_RES_STRING); + r->value.string = s->str; + g_string_free(s, FALSE); + break; } + case ESEXP_RES_TIME: { + time_t total; + + total = argv[0]->value.time; + + for (i = 1; i < argc && argv[i]->type == ESEXP_RES_TIME; i++) + total += argv[i]->value.time; + + if (i < argc) { + e_sexp_resultv_free (f, argc, argv); + e_sexp_fatal_error (f, "Invalid types in (+ time_t)"); + } + + r = e_sexp_result_new (f, ESEXP_RES_TIME); + r->value.time = total; + break; } + } + } + + if (!r) { + r = e_sexp_result_new(f, ESEXP_RES_INT); + r->value.number = 0; + } + return r; +} + +static ESExpResult * +term_eval_sub(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + struct _ESExpResult *r=NULL; + int type; + int i; + + if (argc>0) { + type = argv[0]->type; + switch(type) { + case ESEXP_RES_INT: { + int total = argv[0]->value.number; + for (i=1;i<argc && argv[i]->type == ESEXP_RES_INT;i++) { + total -= argv[i]->value.number; + } + if (i<argc) { + e_sexp_resultv_free(f, argc, argv); + e_sexp_fatal_error(f, "Invalid types in -"); + } + r = e_sexp_result_new(f, ESEXP_RES_INT); + r->value.number = total; + break; } + case ESEXP_RES_TIME: { + time_t total; + + total = argv[0]->value.time; + + for (i = 1; i < argc && argv[i]->type == ESEXP_RES_TIME; i++) + total -= argv[i]->value.time; + + if (i < argc) { + e_sexp_resultv_free (f, argc, argv); + e_sexp_fatal_error (f, "Invalid types in (- time_t)"); + } + + r = e_sexp_result_new (f, ESEXP_RES_TIME); + r->value.time = total; + break; } + } + } + + if (!r) { + r = e_sexp_result_new(f, ESEXP_RES_INT); + r->value.number = 0; + } + return r; +} + +/* cast to int */ +static ESExpResult * +term_eval_castint(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + struct _ESExpResult *r; + + if (argc != 1) + e_sexp_fatal_error(f, "Incorrect argument count to (int )"); + + r = e_sexp_result_new(f, ESEXP_RES_INT); + switch (argv[0]->type) { + case ESEXP_RES_INT: + r->value.number = argv[0]->value.number; + break; + case ESEXP_RES_BOOL: + r->value.number = argv[0]->value.bool != 0; + break; + case ESEXP_RES_STRING: + r->value.number = strtoul(argv[0]->value.string, 0, 10); + break; + default: + e_sexp_result_free(f, r); + e_sexp_fatal_error(f, "Invalid type in (cast-int )"); + } + + return r; +} + +/* cast to string */ +static ESExpResult * +term_eval_caststring(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + struct _ESExpResult *r; + + if (argc != 1) + e_sexp_fatal_error(f, "Incorrect argument count to (cast-string )"); + + r = e_sexp_result_new(f, ESEXP_RES_STRING); + switch (argv[0]->type) { + case ESEXP_RES_INT: + r->value.string = g_strdup_printf("%d", argv[0]->value.number); + break; + case ESEXP_RES_BOOL: + r->value.string = g_strdup_printf("%d", argv[0]->value.bool != 0); + break; + case ESEXP_RES_STRING: + r->value.string = g_strdup(argv[0]->value.string); + break; + default: + e_sexp_result_free(f, r); + e_sexp_fatal_error(f, "Invalid type in (int )"); + } + + return r; +} + +/* implements 'if' function */ +static ESExpResult * +term_eval_if(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r; + int doit; + + if (argc >=2 && argc<=3) { + r = e_sexp_term_eval(f, argv[0]); + doit = (r->type == ESEXP_RES_BOOL && r->value.bool); + e_sexp_result_free(f, r); + if (doit) { + return e_sexp_term_eval(f, argv[1]); + } else if (argc>2) { + return e_sexp_term_eval(f, argv[2]); + } + } + return e_sexp_result_new(f, ESEXP_RES_UNDEFINED); +} + +/* implements 'begin' statement */ +static ESExpResult * +term_eval_begin(struct _ESExp *f, int argc, struct _ESExpTerm **argv, void *data) +{ + struct _ESExpResult *r=NULL; + int i; + + for (i=0;i<argc;i++) { + if (r) + e_sexp_result_free(f, r); + r = e_sexp_term_eval(f, argv[i]); + } + if (r) + return r; + else + return e_sexp_result_new(f, ESEXP_RES_UNDEFINED); +} + + +/* this must only be called from inside term evaluation callbacks! */ +struct _ESExpResult * +e_sexp_term_eval(struct _ESExp *f, struct _ESExpTerm *t) +{ + struct _ESExpResult *r = NULL; + int i; + struct _ESExpResult **argv; + + g_return_val_if_fail(t != NULL, NULL); + + r(printf("eval term :\n")); + r(parse_dump_term(t, 0)); + + switch (t->type) { + case ESEXP_TERM_STRING: + r(printf(" (string \"%s\")\n", t->value.string)); + r = e_sexp_result_new(f, ESEXP_RES_STRING); + /* erk, this shoul;dn't need to strdup this ... */ + r->value.string = g_strdup(t->value.string); + break; + case ESEXP_TERM_INT: + r(printf(" (int %d)\n", t->value.number)); + r = e_sexp_result_new(f, ESEXP_RES_INT); + r->value.number = t->value.number; + break; + case ESEXP_TERM_BOOL: + r(printf(" (int %d)\n", t->value.number)); + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = t->value.bool; + break; + case ESEXP_TERM_TIME: + r(printf(" (time_t %d)\n", t->value.time)); + r = e_sexp_result_new (f, ESEXP_RES_TIME); + r->value.time = t->value.time; + break; + case ESEXP_TERM_IFUNC: + if (t->value.func.sym->f.ifunc) + r = t->value.func.sym->f.ifunc(f, t->value.func.termcount, t->value.func.terms, t->value.func.sym->data); + break; + case ESEXP_TERM_FUNC: + /* first evaluate all arguments to result types */ + argv = alloca(sizeof(argv[0]) * t->value.func.termcount); + for (i=0;i<t->value.func.termcount;i++) { + argv[i] = e_sexp_term_eval(f, t->value.func.terms[i]); + } + /* call the function */ + if (t->value.func.sym->f.func) + r = t->value.func.sym->f.func(f, t->value.func.termcount, argv, t->value.func.sym->data); + + e_sexp_resultv_free(f, t->value.func.termcount, argv); + break; + default: + e_sexp_fatal_error(f, "Unknown type in parse tree: %d", t->type); + } + + if (r==NULL) + r = e_sexp_result_new(f, ESEXP_RES_UNDEFINED); + + return r; +} + +#ifdef TESTER +static void +eval_dump_result(ESExpResult *r, int depth) +{ + int i; + + if (r==NULL) { + printf("null result???\n"); + return; + } + + for (i=0;i<depth;i++) + printf(" "); + + switch (r->type) { + case ESEXP_RES_ARRAY_PTR: + printf("array pointers\n"); + break; + case ESEXP_RES_INT: + printf("int: %d\n", r->value.number); + break; + case ESEXP_RES_STRING: + printf("string: '%s'\n", r->value.string); + break; + case ESEXP_RES_BOOL: + printf("bool: %c\n", r->value.bool?'t':'f'); + break; + case ESEXP_RES_TIME: + printf("time_t: %ld\n", (long) r->value.time); + break; + case ESEXP_RES_UNDEFINED: + printf(" <undefined>\n"); + break; + } + printf("\n"); +} +#endif + +static void +parse_dump_term(struct _ESExpTerm *t, int depth) +{ + int i; + + if (t==NULL) { + printf("null term??\n"); + return; + } + + for (i=0;i<depth;i++) + printf(" "); + + switch (t->type) { + case ESEXP_TERM_STRING: + printf(" \"%s\"", t->value.string); + break; + case ESEXP_TERM_INT: + printf(" %d", t->value.number); + break; + case ESEXP_TERM_BOOL: + printf(" #%c", t->value.bool?'t':'f'); + break; + case ESEXP_TERM_TIME: + printf(" %ld", (long) t->value.time); + break; + case ESEXP_TERM_IFUNC: + case ESEXP_TERM_FUNC: + printf(" (function %s\n", t->value.func.sym->name); + /*printf(" [%d] ", t->value.func.termcount);*/ + for (i=0;i<t->value.func.termcount;i++) { + parse_dump_term(t->value.func.terms[i], depth+1); + } + for (i=0;i<depth;i++) + printf(" "); + printf(" )"); + break; + case ESEXP_TERM_VAR: + printf(" (variable %s )\n", t->value.var->name); + break; + default: + printf("unknown type: %d\n", t->type); + } + + printf("\n"); +} + +/* + PARSER +*/ + +static struct _ESExpTerm * +parse_term_new(struct _ESExp *f, int type) +{ + struct _ESExpTerm *s = e_memchunk_alloc0(f->term_chunks); + s->type = type; + return s; +} + +static void +parse_term_free(struct _ESExp *f, struct _ESExpTerm *t) +{ + int i; + + if (t==NULL) { + return; + } + + switch (t->type) { + case ESEXP_TERM_INT: + case ESEXP_TERM_BOOL: + case ESEXP_TERM_TIME: + case ESEXP_TERM_VAR: + break; + + case ESEXP_TERM_STRING: + g_free(t->value.string); + break; + + case ESEXP_TERM_FUNC: + case ESEXP_TERM_IFUNC: + for (i=0;i<t->value.func.termcount;i++) { + parse_term_free(f, t->value.func.terms[i]); + } + g_free(t->value.func.terms); + break; + + default: + printf("parse_term_free: unknown type: %d\n", t->type); + } + e_memchunk_free(f->term_chunks, t); +} + +static struct _ESExpTerm ** +parse_values(ESExp *f, int *len) +{ + int token; + struct _ESExpTerm **terms; + int i, size = 0; + GScanner *gs = f->scanner; + GSList *list = NULL, *l; + + p(printf("parsing values\n")); + + while ( (token = g_scanner_peek_next_token(gs)) != G_TOKEN_EOF + && token != ')') { + list = g_slist_prepend(list, parse_value(f)); + size++; + } + + /* go over the list, and put them backwards into the term array */ + terms = g_malloc(size * sizeof(*terms)); + l = list; + for (i=size-1;i>=0;i--) { + g_assert(l); + g_assert(l->data); + terms[i] = l->data; + l = g_slist_next(l); + } + g_slist_free(list); + + p(printf("found %d subterms\n", size)); + *len = size; + + p(printf("done parsing values\n")); + return terms; +} + +static struct _ESExpTerm * +parse_value(ESExp *f) +{ + int token, negative = FALSE; + struct _ESExpTerm *t = NULL; + GScanner *gs = f->scanner; + struct _ESExpSymbol *s; + + p(printf("parsing value\n")); + + token = g_scanner_get_next_token(gs); + switch(token) { + case G_TOKEN_LEFT_PAREN: + p(printf("got brace, its a list!\n")); + return parse_list(f, TRUE); + case G_TOKEN_STRING: + p(printf("got string\n")); + t = parse_term_new(f, ESEXP_TERM_STRING); + t->value.string = g_strdup(g_scanner_cur_value(gs).v_string); + break; + case '-': + p(printf ("got negative int?\n")); + token = g_scanner_get_next_token (gs); + if (token != G_TOKEN_INT) { + e_sexp_fatal_error (f, "Invalid format for a integer value"); + return NULL; + } + + negative = TRUE; + /* fall through... */ + case G_TOKEN_INT: + t = parse_term_new(f, ESEXP_TERM_INT); + t->value.number = g_scanner_cur_value(gs).v_int; + if (negative) + t->value.number = -t->value.number; + p(printf("got int\n")); + break; + case '#': { + char *str; + + p(printf("got bool?\n")); + token = g_scanner_get_next_token(gs); + if (token != G_TOKEN_IDENTIFIER) { + e_sexp_fatal_error (f, "Invalid format for a boolean value"); + return NULL; + } + + str = g_scanner_cur_value (gs).v_identifier; + + g_assert (str != NULL); + if (!(strlen (str) == 1 && (str[0] == 't' || str[0] == 'f'))) { + e_sexp_fatal_error (f, "Invalid format for a boolean value"); + return NULL; + } + + t = parse_term_new(f, ESEXP_TERM_BOOL); + t->value.bool = (str[0] == 't'); + break; } + case G_TOKEN_SYMBOL: + s = g_scanner_cur_value(gs).v_symbol; + switch (s->type) { + case ESEXP_TERM_FUNC: + case ESEXP_TERM_IFUNC: + /* this is basically invalid, since we can't use function + pointers, but let the runtime catch it ... */ + t = parse_term_new(f, s->type); + t->value.func.sym = s; + t->value.func.terms = parse_values(f, &t->value.func.termcount); + break; + case ESEXP_TERM_VAR: + t = parse_term_new(f, s->type); + t->value.var = s; + break; + default: + e_sexp_fatal_error(f, "Invalid symbol type: %s: %d", s->name, s->type); + } + break; + case G_TOKEN_IDENTIFIER: + e_sexp_fatal_error(f, "Unknown identifier: %s", g_scanner_cur_value(gs).v_identifier); + break; + default: + e_sexp_fatal_error(f, "Unexpected token encountered: %d", token); + } + p(printf("done parsing value\n")); + return t; +} + +/* FIXME: this needs some robustification */ +static struct _ESExpTerm * +parse_list(ESExp *f, int gotbrace) +{ + int token; + struct _ESExpTerm *t = NULL; + GScanner *gs = f->scanner; + + p(printf("parsing list\n")); + if (gotbrace) + token = '('; + else + token = g_scanner_get_next_token(gs); + if (token =='(') { + token = g_scanner_get_next_token(gs); + switch(token) { + case G_TOKEN_SYMBOL: { + struct _ESExpSymbol *s; + + s = g_scanner_cur_value(gs).v_symbol; + p(printf("got funciton: %s\n", s->name)); + t = parse_term_new(f, s->type); + p(printf("created new list %p\n", t)); + /* if we have a variable, find out its base type */ + while (s->type == ESEXP_TERM_VAR) { + s = ((ESExpTerm *)(s->data))->value.var; + } + if (s->type == ESEXP_TERM_FUNC + || s->type == ESEXP_TERM_IFUNC) { + t->value.func.sym = s; + t->value.func.terms = parse_values(f, &t->value.func.termcount); + } else { + parse_term_free(f, t); + e_sexp_fatal_error(f, "Trying to call variable as function: %s", s->name); + } + break; } + case G_TOKEN_IDENTIFIER: + e_sexp_fatal_error(f, "Unknown identifier: %s", g_scanner_cur_value(gs).v_identifier); + break; + default: + e_sexp_fatal_error(f, "Unexpected token encountered: %d", token); + } + token = g_scanner_get_next_token(gs); + if (token != ')') { + e_sexp_fatal_error(f, "Missing ')'"); + } + } else { + e_sexp_fatal_error(f, "Missing '('"); + } + + p(printf("returning list %p\n", t)); + return t; +} + +static void e_sexp_finalise(void *); + +#ifdef E_SEXP_IS_G_OBJECT +static void +e_sexp_class_init (ESExpClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = e_sexp_finalise; + + parent_class = g_type_class_ref (g_object_get_type ()); +} +#endif + +/* 'builtin' functions */ +static struct { + char *name; + ESExpFunc *func; + int type; /* set to 1 if a function can perform shortcut evaluation, or + doesn't execute everything, 0 otherwise */ +} symbols[] = { + { "and", (ESExpFunc *)term_eval_and, 1 }, + { "or", (ESExpFunc *)term_eval_or, 1 }, + { "not", (ESExpFunc *)term_eval_not, 0 }, + { "<", (ESExpFunc *)term_eval_lt, 1 }, + { ">", (ESExpFunc *)term_eval_gt, 1 }, + { "=", (ESExpFunc *)term_eval_eq, 1 }, + { "+", (ESExpFunc *)term_eval_plus, 0 }, + { "-", (ESExpFunc *)term_eval_sub, 0 }, + { "cast-int", (ESExpFunc *)term_eval_castint, 0 }, + { "cast-string", (ESExpFunc *)term_eval_caststring, 0 }, + { "if", (ESExpFunc *)term_eval_if, 1 }, + { "begin", (ESExpFunc *)term_eval_begin, 1 }, +}; + +static void +free_symbol(void *key, void *value, void *data) +{ + struct _ESExpSymbol *s = value; + + g_free(s->name); + g_free(s); +} + +static void +e_sexp_finalise(void *o) +{ + ESExp *s = (ESExp *)o; + + if (s->tree) { + parse_term_free(s, s->tree); + s->tree = NULL; + } + + e_memchunk_destroy(s->term_chunks); + e_memchunk_destroy(s->result_chunks); + + g_scanner_scope_foreach_symbol(s->scanner, 0, free_symbol, 0); + g_scanner_destroy(s->scanner); + +#ifdef E_SEXP_IS_G_OBJECT + G_OBJECT_CLASS (parent_class)->finalize (o); +#endif +} + +static void +e_sexp_init (ESExp *s) +{ + int i; + + s->scanner = g_scanner_new(&scanner_config); + s->term_chunks = e_memchunk_new(16, sizeof(struct _ESExpTerm)); + s->result_chunks = e_memchunk_new(16, sizeof(struct _ESExpResult)); + + /* load in builtin symbols? */ + for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) { + if (symbols[i].type == 1) { + e_sexp_add_ifunction(s, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, &symbols[i]); + } else { + e_sexp_add_function(s, 0, symbols[i].name, symbols[i].func, &symbols[i]); + } + } + +#ifndef E_SEXP_IS_G_OBJECT + s->refcount = 1; +#endif +} + +#ifdef E_SEXP_IS_G_OBJECT +GType +e_sexp_get_type (void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof (ESExpClass), + NULL, /* base_class_init */ + NULL, /* base_class_finalize */ + (GClassInitFunc) e_sexp_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (ESExp), + 0, /* n_preallocs */ + (GInstanceInitFunc) e_sexp_init, + }; + + type = g_type_register_static (G_TYPE_OBJECT, "ESExp", &info, 0); + } + + return type; +} +#endif + +ESExp * +e_sexp_new (void) +{ +#ifdef E_SEXP_IS_G_OBJECT + ESExp *f = (ESexp *) g_object_new (E_TYPE_SEXP, NULL); +#else + ESExp *f = g_malloc0 (sizeof (ESExp)); + e_sexp_init (f); +#endif + + return f; +} + +#ifndef E_SEXP_IS_G_OBJECT +void +e_sexp_ref (ESExp *f) +{ + f->refcount++; +} + +void +e_sexp_unref (ESExp *f) +{ + f->refcount--; + if (f->refcount == 0) { + e_sexp_finalise(f); + g_free(f); + } +} +#endif + +void +e_sexp_add_function(ESExp *f, int scope, char *name, ESExpFunc *func, void *data) +{ + struct _ESExpSymbol *s; + + g_return_if_fail (IS_E_SEXP (f)); + g_return_if_fail (name != NULL); + + e_sexp_remove_symbol (f, scope, name); + + s = g_malloc0(sizeof(*s)); + s->name = g_strdup(name); + s->f.func = func; + s->type = ESEXP_TERM_FUNC; + s->data = data; + g_scanner_scope_add_symbol(f->scanner, scope, s->name, s); +} + +void +e_sexp_add_ifunction(ESExp *f, int scope, char *name, ESExpIFunc *ifunc, void *data) +{ + struct _ESExpSymbol *s; + + g_return_if_fail (IS_E_SEXP (f)); + g_return_if_fail (name != NULL); + + e_sexp_remove_symbol (f, scope, name); + + s = g_malloc0(sizeof(*s)); + s->name = g_strdup(name); + s->f.ifunc = ifunc; + s->type = ESEXP_TERM_IFUNC; + s->data = data; + g_scanner_scope_add_symbol(f->scanner, scope, s->name, s); +} + +void +e_sexp_add_variable(ESExp *f, int scope, char *name, ESExpTerm *value) +{ + struct _ESExpSymbol *s; + + g_return_if_fail (IS_E_SEXP (f)); + g_return_if_fail (name != NULL); + + s = g_malloc0(sizeof(*s)); + s->name = g_strdup(name); + s->type = ESEXP_TERM_VAR; + s->data = value; + g_scanner_scope_add_symbol(f->scanner, scope, s->name, s); +} + +void +e_sexp_remove_symbol(ESExp *f, int scope, char *name) +{ + int oldscope; + struct _ESExpSymbol *s; + + g_return_if_fail (IS_E_SEXP (f)); + g_return_if_fail (name != NULL); + + oldscope = g_scanner_set_scope(f->scanner, scope); + s = g_scanner_lookup_symbol(f->scanner, name); + g_scanner_scope_remove_symbol(f->scanner, scope, name); + g_scanner_set_scope(f->scanner, oldscope); + if (s) { + g_free(s->name); + g_free(s); + } +} + +int +e_sexp_set_scope(ESExp *f, int scope) +{ + g_return_val_if_fail (IS_E_SEXP (f), 0); + + return g_scanner_set_scope(f->scanner, scope); +} + +void +e_sexp_input_text(ESExp *f, const char *text, int len) +{ + g_return_if_fail (IS_E_SEXP (f)); + g_return_if_fail (text != NULL); + + g_scanner_input_text(f->scanner, text, len); +} + +void +e_sexp_input_file (ESExp *f, int fd) +{ + g_return_if_fail (IS_E_SEXP (f)); + + g_scanner_input_file(f->scanner, fd); +} + +/* returns -1 on error */ +int +e_sexp_parse(ESExp *f) +{ + g_return_val_if_fail (IS_E_SEXP (f), -1); + + if (setjmp(f->failenv)) { + g_warning("Error in parsing: %s", f->error); + return -1; + } + + if (f->tree) + parse_term_free(f, f->tree); + + f->tree = parse_value (f); + + return 0; +} + +/* returns NULL on error */ +struct _ESExpResult * +e_sexp_eval(ESExp *f) +{ + g_return_val_if_fail (IS_E_SEXP (f), NULL); + g_return_val_if_fail (f->tree != NULL, NULL); + + if (setjmp(f->failenv)) { + g_warning("Error in execution: %s", f->error); + return NULL; + } + + return e_sexp_term_eval(f, f->tree); +} + +/** + * e_sexp_encode_bool: + * @s: + * @state: + * + * Encode a bool into an s-expression @s. Bools are + * encoded using #t #f syntax. + **/ +void +e_sexp_encode_bool(GString *s, gboolean state) +{ + if (state) + g_string_append(s, " #t"); + else + g_string_append(s, " #f"); +} + +/** + * e_sexp_encode_string: + * @s: Destination string. + * @string: String expression. + * + * Add a c string @string to the s-expression stored in + * the gstring @s. Quotes are added, and special characters + * are escaped appropriately. + **/ +void +e_sexp_encode_string(GString *s, const char *string) +{ + char c; + const char *p; + + if (string == NULL) + p = ""; + else + p = string; + g_string_append(s, " \""); + while ( (c = *p++) ) { + if (c=='\\' || c=='\"' || c=='\'') + g_string_append_c(s, '\\'); + g_string_append_c(s, c); + } + g_string_append(s, "\""); +} + +#ifdef TESTER +int main(int argc, char **argv) +{ + ESExp *f; + char *t = "(+ \"foo\" \"\\\"\" \"bar\" \"\\\\ blah \\x \")"; + ESExpResult *r; + + gtk_init(&argc, &argv); + + f = e_sexp_new(); + + e_sexp_add_variable(f, 0, "test", NULL); + + e_sexp_input_text(f, t, strlen(t)); + e_sexp_parse(f); + + if (f->tree) { + parse_dump_term(f->tree, 0); + } + + r = e_sexp_eval(f); + if (r) { + eval_dump_result(r, 0); + } else { + printf("no result?|\n"); + } + + return 0; +} +#endif diff --git a/libedataserver/e-sexp.h b/libedataserver/e-sexp.h new file mode 100644 index 000000000..5f41c97dc --- /dev/null +++ b/libedataserver/e-sexp.h @@ -0,0 +1,172 @@ +/* + generic s-exp evaluator class +*/ +#ifndef _E_SEXP_H +#define _E_SEXP_H + +#include <setjmp.h> +#include <time.h> +#include <glib.h> + +#ifdef E_SEXP_IS_G_OBJECT +#include <glib-object.h> +#endif + +#ifdef E_SEXP_IS_G_OBJECT +#define E_TYPE_SEXP (e_sexp_get_type ()) +#define E_SEXP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_SEXP, ESExp)) +#define E_SEXP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_SEXP, ESExpClass)) +#define IS_E_SEXP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_SEXP)) +#define IS_E_SEXP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_SEXP)) +#define E_SEXP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_SEXP, ESExpClass)) +#else +#define E_TYPE_SEXP (0) +#define E_SEXP(obj) ((struct _ESExp *) (obj)) +#define E_SEXP_CLASS(klass) ((struct _ESExpClass *) (klass)) +#define IS_E_SEXP(obj) (1) +#define IS_E_SEXP_CLASS(obj) (1) +#define E_SEXP_GET_CLASS(obj) (NULL) +#endif + +typedef struct _ESExp ESExp; +typedef struct _ESExpClass ESExpClass; + +typedef struct _ESExpSymbol ESExpSymbol; +typedef struct _ESExpResult ESExpResult; +typedef struct _ESExpTerm ESExpTerm; + +typedef struct _ESExpResult *(ESExpFunc)(struct _ESExp *sexp, int argc, + struct _ESExpResult **argv, + void *data); + +typedef struct _ESExpResult *(ESExpIFunc)(struct _ESExp *sexp, int argc, + struct _ESExpTerm **argv, + void *data); + +enum _ESExpResultType { + ESEXP_RES_ARRAY_PTR=0, /* type is a ptrarray, what it points to is implementation dependant */ + ESEXP_RES_INT, /* type is a number */ + ESEXP_RES_STRING, /* type is a pointer to a single string */ + ESEXP_RES_BOOL, /* boolean type */ + ESEXP_RES_TIME, /* time_t type */ + ESEXP_RES_UNDEFINED /* unknown type */ +}; + +struct _ESExpResult { + enum _ESExpResultType type; + union { + GPtrArray *ptrarray; + int number; + char *string; + int bool; + time_t time; + } value; +}; + +enum _ESExpTermType { + ESEXP_TERM_INT = 0, /* integer literal */ + ESEXP_TERM_BOOL, /* boolean literal */ + ESEXP_TERM_STRING, /* string literal */ + ESEXP_TERM_TIME, /* time_t literal (number of seconds past the epoch) */ + ESEXP_TERM_FUNC, /* normal function, arguments are evaluated before calling */ + ESEXP_TERM_IFUNC, /* immediate function, raw terms are arguments */ + ESEXP_TERM_VAR, /* variable reference */ +}; + +struct _ESExpSymbol { + int type; /* ESEXP_TERM_FUNC or ESEXP_TERM_VAR */ + char *name; + void *data; + union { + ESExpFunc *func; + ESExpIFunc *ifunc; + } f; +}; + +struct _ESExpTerm { + enum _ESExpTermType type; + union { + char *string; + int number; + int bool; + time_t time; + struct { + struct _ESExpSymbol *sym; + struct _ESExpTerm **terms; + int termcount; + } func; + struct _ESExpSymbol *var; + } value; +}; + + + +struct _ESExp { +#ifdef E_SEXP_IS_G_OBJECT + GObject parent_object; +#else + int refcount; +#endif + GScanner *scanner; /* for parsing text version */ + ESExpTerm *tree; /* root of expression tree */ + + /* private stuff */ + jmp_buf failenv; + char *error; + + /* TODO: may also need a pool allocator for term strings, so we dont lose them + in error conditions? */ + struct _EMemChunk *term_chunks; + struct _EMemChunk *result_chunks; +}; + +struct _ESExpClass { +#ifdef E_SEXP_IS_G_OBJECT + GObjectClass parent_class; +#else + int dummy; +#endif +}; + +#ifdef E_SEXP_IS_G_OBJECT +GType e_sexp_get_type (void); +#endif +ESExp *e_sexp_new (void); +#ifdef E_SEXP_IS_G_OBJECT +#define e_sexp_ref(f) g_object_ref (f) +#define e_sexp_unref(f) g_object_unref (f) +#else +void e_sexp_ref (ESExp *f); +void e_sexp_unref (ESExp *f); +#endif +void e_sexp_add_function (ESExp *f, int scope, char *name, ESExpFunc *func, void *data); +void e_sexp_add_ifunction (ESExp *f, int scope, char *name, ESExpIFunc *func, void *data); +void e_sexp_add_variable (ESExp *f, int scope, char *name, ESExpTerm *value); +void e_sexp_remove_symbol (ESExp *f, int scope, char *name); +int e_sexp_set_scope (ESExp *f, int scope); + +void e_sexp_input_text (ESExp *f, const char *text, int len); +void e_sexp_input_file (ESExp *f, int fd); + + +int e_sexp_parse (ESExp *f); +ESExpResult *e_sexp_eval (ESExp *f); + +ESExpResult *e_sexp_term_eval (struct _ESExp *f, struct _ESExpTerm *t); +ESExpResult *e_sexp_result_new (struct _ESExp *f, int type); +void e_sexp_result_free (struct _ESExp *f, struct _ESExpResult *t); + +/* used in normal functions if they have to abort, to free their arguments */ +void e_sexp_resultv_free (struct _ESExp *f, int argc, struct _ESExpResult **argv); + +/* utility functions for creating s-exp strings. */ +void e_sexp_encode_bool (GString *s, gboolean state); +void e_sexp_encode_string (GString *s, const char *string); + +/* only to be called from inside a callback to signal a fatal execution error */ +void e_sexp_fatal_error (struct _ESExp *f, char *why, ...); + +/* return the error string */ +const char *e_sexp_error (struct _ESExp *f); + +#endif /* _E_SEXP_H */ diff --git a/libedataserver/e-uid.c b/libedataserver/e-uid.c new file mode 100644 index 000000000..90c036e0c --- /dev/null +++ b/libedataserver/e-uid.c @@ -0,0 +1,61 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-uid.c - Unique ID generator. + * + * Copyright (C) 2002 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + * + * Author: Dan Winship <danw@ximian.com> + */ + +#include "e-uid.h" + +#include <glib/gstrfuncs.h> + +#include <string.h> +#include <time.h> +#include <unistd.h> + + +/** + * e_uid_new: + * + * Generate a new unique string for use e.g. in account lists. + * + * Return value: the newly generated UID. The caller should free the string + * when it's done with it. + **/ +char * +e_uid_new (void) +{ + static char *hostname; + static int serial; + + if (!hostname) { + static char buffer [512]; + + if ((gethostname (buffer, sizeof (buffer) - 1) == 0) && + (buffer [0] != 0)) + hostname = buffer; + else + hostname = "localhost"; + } + + return g_strdup_printf ("%lu.%lu.%d@%s", + (unsigned long) time (NULL), + (unsigned long) getpid (), + serial++, + hostname); +} diff --git a/libedataserver/e-uid.h b/libedataserver/e-uid.h new file mode 100644 index 000000000..44ec8c0dd --- /dev/null +++ b/libedataserver/e-uid.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-uid.h - Unique ID generator. + * + * Copyright (C) 2002 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + * + * Author: Dan Winship <danw@ximian.com> + */ + +#ifndef E_UID_H +#define E_UID_H + +char *e_uid_new (void); + +#endif /* E_UID_H */ diff --git a/libedataserver/e-url.c b/libedataserver/e-url.c new file mode 100644 index 000000000..8dba46b62 --- /dev/null +++ b/libedataserver/e-url.c @@ -0,0 +1,341 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * e-url.c + * + * Copyright (C) 2001 Ximian, Inc. + * + * Developed by Jon Trowbridge <trow@ximian.com> + * Rodrigo Moya <rodrigo@ximian.com> + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include "e-url.h" + +char * +e_url_shroud (const char *url) +{ + const char *first_colon = NULL; + const char *last_at = NULL; + const char *p; + char *shrouded; + + if (url == NULL) + return NULL; + + /* Skip past the moniker */ + for (p = url; *p && *p != ':'; ++p); + if (*p) + ++p; + + while (*p) { + if (first_colon == NULL && *p == ':') + first_colon = p; + if (*p == '@') + last_at = p; + ++p; + } + + if (first_colon && last_at && first_colon < last_at) { + shrouded = g_strdup_printf ("%.*s%s", first_colon - url, url, last_at); + } else { + shrouded = g_strdup (url); + } + + return shrouded; +} + +gboolean +e_url_equal (const char *url1, const char *url2) +{ + char *shroud1 = e_url_shroud (url1); + char *shroud2 = e_url_shroud (url2); + gint len1, len2; + gboolean rv; + + if (shroud1 == NULL || shroud2 == NULL) { + rv = (shroud1 == shroud2); + } else { + len1 = strlen (shroud1); + len2 = strlen (shroud2); + + rv = !strncmp (shroud1, shroud2, MIN (len1, len2)); + } + + g_free (shroud1); + g_free (shroud2); + + return rv; +} + +#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10) + +static void +uri_decode (char *part) +{ + guchar *s, *d; + + s = d = (guchar *)part; + while (*s) { + if (*s == '%') { + if (isxdigit (s[1]) && isxdigit (s[2])) { + *d++ = HEXVAL (s[1]) * 16 + HEXVAL (s[2]); + s += 3; + } else + *d++ = *s++; + } else + *d++ = *s++; + } + *d = '\0'; +} + +EUri * +e_uri_new (const char *uri_string) +{ + EUri *uri; + const char *end, *hash, *colon, *semi, *at, *slash, *question; + const char *p; + + if (!uri_string) + return NULL; + + uri = g_new0 (EUri, 1); + + /* find fragment */ + end = hash = strchr (uri_string, '#'); + if (hash && hash[1]) { + uri->fragment = g_strdup (hash + 1); + uri_decode (uri->fragment); + } + else + end = uri_string + strlen (uri_string); + + /* find protocol: initial [a-z+.-]* substring until ":" */ + p = uri_string; + while (p < end && (isalnum ((unsigned char) *p) || + *p == '.' || *p == '+' || *p == '-')) + p++; + + if (p > uri_string && *p == ':') { + uri->protocol = g_ascii_strdown (uri_string, p - uri_string); + uri_string = p + 1; + } + else + uri->protocol = g_strdup ("file"); + + if (!*uri_string) + return uri; + + /* check for authority */ + if (strncmp (uri_string, "//", 2) == 0) { + uri_string += 2; + + slash = uri_string + strcspn (uri_string, "/#"); + at = strchr (uri_string, '@'); + if (at && at < slash) { + colon = strchr (uri_string, ':'); + if (colon && colon < at) { + uri->passwd = g_strndup (colon + 1, at - colon - 1); + uri_decode (uri->passwd); + } + else { + uri->passwd = NULL; + colon = at; + } + + semi = strchr (uri_string, ';'); + if (semi && semi < colon && + !strncasecmp (semi, ";auth=", 6)) { + uri->authmech = g_strndup (semi + 6, colon - semi - 6); + uri_decode (uri->authmech); + } + else { + uri->authmech = NULL; + semi = colon; + } + + uri->user = g_strndup (uri_string, semi - uri_string); + uri_decode (uri->user); + uri_string = at + 1; + } + else + uri->user = uri->passwd = uri->authmech = NULL; + + /* find host and port */ + colon = strchr (uri_string, ':'); + if (colon && colon < slash) { + uri->host = g_strndup (uri_string, colon - uri_string); + uri->port = strtoul (colon + 1, NULL, 10); + } + else { + uri->host = g_strndup (uri_string, slash - uri_string); + uri_decode (uri->host); + uri->port = 0; + } + + uri_string = slash; + } + + /* find query */ + question = memchr (uri_string, '?', end - uri_string); + if (question) { + if (question[1]) { + uri->query = g_strndup (question + 1, end - (question + 1)); + uri_decode (uri->query); + } + end = question; + } + + /* find parameters */ + semi = memchr (uri_string, ';', end - uri_string); + if (semi) { + if (semi[1]) { + const char *cur, *p, *eq; + char *name, *value; + + for (cur = semi + 1; cur < end; cur = p + 1) { + p = memchr (cur, ';', end - cur); + if (!p) + p = end; + eq = memchr (cur, '=', p - cur); + if (eq) { + name = g_strndup (cur, eq - cur); + value = g_strndup (eq + 1, p - (eq + 1)); + uri_decode (value); + } else { + name = g_strndup (cur, p - cur); + value = g_strdup (""); + } + uri_decode (name); + g_datalist_set_data_full (&uri->params, name, + value, g_free); + g_free (name); + } + } + end = semi; + } + + if (end != uri_string) { + uri->path = g_strndup (uri_string, end - uri_string); + uri_decode (uri->path); + } + + return uri; +} + +void +e_uri_free (EUri *uri) +{ + if (uri) { + g_free (uri->protocol); + g_free (uri->user); + g_free (uri->authmech); + g_free (uri->passwd); + g_free (uri->host); + g_free (uri->path); + g_datalist_clear (&uri->params); + g_free (uri->query); + g_free (uri->fragment); + + g_free (uri); + } +} + +const char * +e_uri_get_param (EUri *uri, const char *name) +{ + return g_datalist_get_data (&uri->params, name); +} + +static void +copy_param_cb (GQuark key_id, gpointer data, gpointer user_data) +{ + GData *params = (GData *) user_data; + + g_datalist_id_set_data_full (¶ms, key_id, g_strdup (data), g_free); +} + +EUri * +e_uri_copy (EUri *uri) +{ + EUri *uri_copy; + + g_return_val_if_fail (uri != NULL, NULL); + + uri_copy = g_new0 (EUri, 1); + uri_copy->protocol = g_strdup (uri->protocol); + uri_copy->user = g_strdup (uri->user); + uri_copy->authmech = g_strdup (uri->authmech); + uri_copy->passwd = g_strdup (uri->passwd); + uri_copy->host = g_strdup (uri->host); + uri_copy->port = uri->port; + uri_copy->path = g_strdup (uri->path); + uri_copy->query = g_strdup (uri->query); + uri_copy->fragment = g_strdup (uri->fragment); + + /* copy uri->params */ + g_datalist_foreach (&uri->params, + (GDataForeachFunc) copy_param_cb, + &uri_copy->params); + + return uri_copy; +} + +char * +e_uri_to_string (EUri *uri, gboolean show_password) +{ + char *str_uri = NULL; + + g_return_val_if_fail (uri != NULL, NULL); + + if (uri->port != 0) + str_uri = g_strdup_printf ( + "%s://%s%s%s%s%s%s%s:%d%s%s%s", + uri->protocol, + uri->user ? uri->user : "", + uri->authmech ? ";auth=" : "", + uri->authmech ? uri->authmech : "", + uri->passwd && show_password ? ":" : "", + uri->passwd && show_password ? uri->passwd : "", + uri->user ? "@" : "", + uri->host ? uri->host : "", + uri->port, + uri->path ? uri->path : "", + uri->query ? "?" : "", + uri->query ? uri->query : ""); + else + str_uri = g_strdup_printf( + "%s://%s%s%s%s%s%s%s%s%s%s", + uri->protocol, + uri->user ? uri->user : "", + uri->authmech ? ";auth=" : "", + uri->authmech ? uri->authmech : "", + uri->passwd && show_password ? ":" : "", + uri->passwd && show_password ? uri->passwd : "", + uri->user ? "@" : "", + uri->host ? uri->host : "", + uri->path ? uri->path : "", + uri->query ? "?" : "", + uri->query ? uri->query : ""); + + return str_uri; +} diff --git a/libedataserver/e-url.h b/libedataserver/e-url.h new file mode 100644 index 000000000..205ad3cfc --- /dev/null +++ b/libedataserver/e-url.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * e-url.h + * + * Copyright (C) 2001 Ximian, Inc. + * + * Developed by Jon Trowbridge <trow@ximian.com> + * Rodrigo Moya <rodrigo@ximian.com> + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef __E_URL_H__ +#define __E_URL_H__ + +#include <glib.h> + +char *e_url_shroud (const char *url); +gboolean e_url_equal (const char *url1, const char *url2); + +typedef struct { + char *protocol; + char *user; + char *authmech; + char *passwd; + char *host; + int port; + char *path; + GData *params; + char *query; + char *fragment; +} EUri; + +EUri *e_uri_new (const char *uri_string); +void e_uri_free (EUri *uri); +const char *e_uri_get_param (EUri *uri, const char *name); +EUri *e_uri_copy (EUri *uri); +char *e_uri_to_string (EUri *uri, gboolean show_password); + +#endif /* __E_URL_H__ */ + diff --git a/libedataserver/e-xml-hash-utils.c b/libedataserver/e-xml-hash-utils.c new file mode 100644 index 000000000..ba72321af --- /dev/null +++ b/libedataserver/e-xml-hash-utils.c @@ -0,0 +1,252 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 "e-xml-hash-utils.h" + +#include <stdlib.h> +#include <string.h> +#include <libxml/xmlmemory.h> +#include <libxml/entities.h> + +GHashTable * +e_xml_to_hash (xmlDoc *doc, EXmlHashType type) +{ + xmlNode *root, *node; + const char *key; + xmlChar *value; + GHashTable *hash; + + hash = g_hash_table_new (g_str_hash, g_str_equal); + + root = xmlDocGetRootElement (doc); + for (node = root->xmlChildrenNode; node; node = node->next) { + if (node->name == NULL || node->type != XML_ELEMENT_NODE) + continue; + + if (type == E_XML_HASH_TYPE_OBJECT_UID && + !strcmp (node->name, "object")) + key = xmlGetProp (node, "uid"); + else + key = node->name; + + value = xmlNodeListGetString (doc, node->xmlChildrenNode, 1); + if (!key || !value) { + g_warning ("Found an entry with missing properties!!"); + continue; + } + + g_hash_table_insert (hash, g_strdup (key), g_strdup (value)); + xmlFree (value); + } + + return hash; +} + + +struct save_data { + EXmlHashType type; + xmlDoc *doc; + xmlNode *root; +}; + +static void +foreach_save_func (gpointer key, gpointer value, gpointer user_data) +{ + struct save_data *sd = user_data; + xmlNodePtr new_node; + xmlChar *enc; + + if (sd->type == E_XML_HASH_TYPE_OBJECT_UID) { + new_node = xmlNewNode (NULL, "object"); + xmlNewProp (new_node, "uid", (const char *) key); + } else + new_node = xmlNewNode (NULL, (const char *) key); + + enc = xmlEncodeSpecialChars (sd->doc, value); + xmlNodeSetContent (new_node, enc); + xmlFree (enc); + + xmlAddChild (sd->root, new_node); +} + +xmlDoc * +e_xml_from_hash (GHashTable *hash, EXmlHashType type, const char *root_name) +{ + xmlDoc *doc; + struct save_data sd; + + doc = xmlNewDoc ("1.0"); + sd.type = type; + sd.doc = doc; + sd.root = xmlNewDocNode (doc, NULL, root_name, NULL); + xmlDocSetRootElement (doc, sd.root); + + g_hash_table_foreach (hash, foreach_save_func, &sd); + return doc; +} + +static void +free_values (gpointer key, gpointer value, gpointer data) +{ + g_free (key); + g_free (value); +} + +void +e_xml_destroy_hash (GHashTable *hash) +{ + g_hash_table_foreach (hash, free_values, NULL); + g_hash_table_destroy (hash); +} + + + +struct EXmlHash { + char *filename; + GHashTable *objects; +}; + +EXmlHash * +e_xmlhash_new (const char *filename) +{ + EXmlHash *hash; + xmlDoc *doc = NULL; + + g_return_val_if_fail (filename != NULL, NULL); + + hash = g_new0 (EXmlHash, 1); + hash->filename = g_strdup (filename); + + if (g_file_test (filename, G_FILE_TEST_EXISTS)) { + doc = xmlParseFile (filename); + if (!doc) { + e_xmlhash_destroy (hash); + + return NULL; + } + hash->objects = e_xml_to_hash (doc, E_XML_HASH_TYPE_OBJECT_UID); + xmlFreeDoc (doc); + } else { + hash->objects = g_hash_table_new (g_str_hash, g_str_equal); + } + + return hash; +} + +void +e_xmlhash_add (EXmlHash *hash, const char *key, const char *data) +{ + g_return_if_fail (hash != NULL); + g_return_if_fail (key != NULL); + g_return_if_fail (data != NULL); + + e_xmlhash_remove (hash, key); + g_hash_table_insert (hash->objects, g_strdup (key), g_strdup (data)); +} + +void +e_xmlhash_remove (EXmlHash *hash, const char *key) +{ + gpointer orig_key; + gpointer orig_value; + + g_return_if_fail (hash != NULL); + g_return_if_fail (key != NULL); + + if (g_hash_table_lookup_extended (hash->objects, key, &orig_key, &orig_value)) { + g_hash_table_remove (hash->objects, key); + g_free (orig_key); + g_free (orig_value); + } +} + +EXmlHashStatus +e_xmlhash_compare (EXmlHash *hash, const char *key, const char *compare_data) +{ + char *data; + int rc; + + g_return_val_if_fail (hash != NULL, E_XMLHASH_STATUS_NOT_FOUND); + g_return_val_if_fail (key != NULL, E_XMLHASH_STATUS_NOT_FOUND); + g_return_val_if_fail (compare_data != NULL, E_XMLHASH_STATUS_NOT_FOUND); + + data = g_hash_table_lookup (hash->objects, key); + if (!data) + return E_XMLHASH_STATUS_NOT_FOUND; + + rc = strcmp (data, compare_data); + if (rc == 0) + return E_XMLHASH_STATUS_SAME; + + return E_XMLHASH_STATUS_DIFFERENT; +} + +typedef struct { + EXmlHashFunc func; + gpointer user_data; +} foreach_data_t; + +static void +foreach_hash_func (gpointer key, gpointer value, gpointer user_data) +{ + foreach_data_t *data = (foreach_data_t *) user_data; + + data->func (key, data->user_data); +} + +void +e_xmlhash_foreach_key (EXmlHash *hash, EXmlHashFunc func, gpointer user_data) +{ + foreach_data_t data; + + g_return_if_fail (hash != NULL); + g_return_if_fail (func != NULL); + + data.func = func; + data.user_data = user_data; + g_hash_table_foreach (hash->objects, foreach_hash_func, &data); +} + +void +e_xmlhash_write (EXmlHash *hash) +{ + xmlDoc *doc; + + g_return_if_fail (hash != NULL); + + doc = e_xml_from_hash (hash->objects, E_XML_HASH_TYPE_OBJECT_UID, "xmlhash"); + xmlSaveFile (hash->filename, doc); + xmlFreeDoc (doc); +} + +void +e_xmlhash_destroy (EXmlHash *hash) +{ + g_return_if_fail (hash != NULL); + + g_free (hash->filename); + if (hash->objects) + e_xml_destroy_hash (hash->objects); + + g_free (hash); +} diff --git a/libedataserver/e-xml-hash-utils.h b/libedataserver/e-xml-hash-utils.h new file mode 100644 index 000000000..b972aa4b1 --- /dev/null +++ b/libedataserver/e-xml-hash-utils.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef __E_XML_HASH_UTILS_H__ +#define __E_XML_HASH_UTILS_H__ + +#include <glib.h> +#include <libxml/parser.h> + +typedef enum { + E_XML_HASH_TYPE_OBJECT_UID, + E_XML_HASH_TYPE_PROPERTY +} EXmlHashType; + +GHashTable *e_xml_to_hash (xmlDoc *doc, + EXmlHashType type); +xmlDoc *e_xml_from_hash (GHashTable *hash, + EXmlHashType type, + const char *root_node); + +void e_xml_destroy_hash (GHashTable *hash); + + + +typedef enum { + E_XMLHASH_STATUS_SAME, + E_XMLHASH_STATUS_DIFFERENT, + E_XMLHASH_STATUS_NOT_FOUND +} EXmlHashStatus; + +typedef void (* EXmlHashFunc) (const char *key, gpointer user_data); + +typedef struct EXmlHash EXmlHash; + +EXmlHash *e_xmlhash_new (const char *filename); + +void e_xmlhash_add (EXmlHash *hash, + const char *key, + const char *data); +void e_xmlhash_remove (EXmlHash *hash, + const char *key); + +EXmlHashStatus e_xmlhash_compare (EXmlHash *hash, + const char *key, + const char *compare_data); +void e_xmlhash_foreach_key (EXmlHash *hash, + EXmlHashFunc func, + gpointer user_data); + +void e_xmlhash_write (EXmlHash *hash); +void e_xmlhash_destroy (EXmlHash *hash); + +#endif diff --git a/libedataserver/ename/Makefile.am b/libedataserver/ename/Makefile.am new file mode 100644 index 000000000..384693160 --- /dev/null +++ b/libedataserver/ename/Makefile.am @@ -0,0 +1,45 @@ +INCLUDES = \ + -DG_LOG_DOMAIN=\"EName\" \ + -I$(srcdir) \ + -I$(srcdir)/.. \ + -I$(top_srcdir) \ + -I. \ + -I.. \ + -I$(top_builddir) \ + $(E_NAME_CFLAGS) + +lib_LTLIBRARIES = libename.la + +libename_la_SOURCES = \ + e-address-western.c \ + e-name-western.c + +libename_la_LDFLAGS = \ + -version-info $(LIBEDATASERVER_CURRENT):$(LIBEDATASERVER_REVISION):$(LIBEDATASERVER_AGE) \ + -no-undefined + +libenameincludedir = $(includedir)/libedataserver-1.0/libename + +libenameinclude_HEADERS = \ + e-address-western.h \ + e-name-western-tables.h \ + e-name-western.h + + +#noinst_PROGRAMS = \ +# test-ename-western \ +# test-ename-western-gtk + +#test_ename_western_SOURCES = \ +# test-ename-western.c + +#test_ename_western_LDADD = \ +# $(ename_libs) + +#test_ename_western_gtk_SOURCES = \ +# test-ename-western-gtk.c + +#test_ename_western_gtk_LDADD = \ +# $(ename_libs) \ +# $(E_UTIL_LIBS) \ +# $(top_builddir)/e-util/libeutil.la diff --git a/libedataserver/ename/TODO b/libedataserver/ename/TODO new file mode 100644 index 000000000..669661eea --- /dev/null +++ b/libedataserver/ename/TODO @@ -0,0 +1,2 @@ +* Support other naming systems. +* Handle misspelled suffixes better. diff --git a/libedataserver/ename/e-address-western.c b/libedataserver/ename/e-address-western.c new file mode 100644 index 000000000..9d325f138 --- /dev/null +++ b/libedataserver/ename/e-address-western.c @@ -0,0 +1,444 @@ +/* -------------------------------------------------- + + An address parser, yielding fields as per RFC 2426. + + Author: + Jesse Pavel (jpavel@ximian.com) + + Copyright 2000, Ximian, Inc. + -------------------------------------------------- +*/ + +#include <ctype.h> +#include <string.h> +#include <glib.h> + +#ifdef E_ADDRESS_WESTERN_TEST + +#include "e-address-western.h" + +#else + +#include <ename/e-address-western.h> +#include <gal/util/e-util.h> + +#endif + +/* These are the keywords that will distinguish the start of an extended + address. */ + +static char *extended_keywords[] = { + "apt", "apartment", "suite", NULL +}; + + + +static gboolean +e_address_western_is_line_blank (gchar *line) +{ + gboolean blank = TRUE; + gint cntr; + + /* A blank line consists of whitespace only, or a NULL line. */ + for (cntr = 0; line[cntr] != '\0'; cntr++ ) { + if (!isspace(line[cntr])) { + blank = FALSE; + break; + } + } + + return blank; +} + + + +/* In the array of lines, `lines', we will erase the line at line_num, and + shift the remaining lines, up to line number num_lines, up one position. */ + +static void +e_address_western_shift_line (gchar *lines[], gint line_num, gint num_lines) +{ + gint cntr; + + if (line_num >= (num_lines - 1)) { + /* It is the last line, so simply shift in a NULL. */ + lines[line_num] = NULL; + } + else { + for (cntr = line_num; cntr < num_lines; cntr++) + lines[cntr] = lines[cntr + 1]; + } +} + + +static void +e_address_western_remove_blank_lines (gchar *lines[], gint *linecntr) +{ + gint cntr; + + for (cntr = 0; cntr < *linecntr; cntr++) { + if (e_address_western_is_line_blank (lines[cntr])) { + /* Delete the blank line, and shift all subsequent lines up + one spot to fill its old spot. */ + e_address_western_shift_line (lines, cntr, *linecntr); + + /* Since we must check the newly shifted line, let's + not advance the counter on this next pass. */ + cntr--; + + /* There is now one less line, total. */ + *linecntr -= 1; + } + } +} + + +static gboolean +e_address_western_is_po_box (gchar *line) +{ + gboolean retval = FALSE; + + /* In which phase of processing are we? */ + enum State { FIRSTCHAR, SECONDCHAR, WHITESPACE } state; + + + /* If the first two letters of the line are `p' and `o', and these + are in turn followed by whitespace before another letter, then I + will deem the line a representation of a PO Box address. */ + + gint cntr; + + state = FIRSTCHAR; + for (cntr = 0; line[cntr] != '\0'; cntr++) { + if (state == FIRSTCHAR) { + if (isalnum(line[cntr])) { + if (tolower(line[cntr]) == 'p') + state = SECONDCHAR; + else { + retval = FALSE; + break; + } + } + } + else if (state == SECONDCHAR) { + if (isalnum (line[cntr])) { + if (tolower(line[cntr]) == 'o') + state = WHITESPACE; + else { + retval = FALSE; + break; + } + } + } + else if (state == WHITESPACE) { + if (isspace (line[cntr])) { + retval = TRUE; + break; + } + else if (isalnum (line[cntr])) { + retval = FALSE; + break; + } + } + } + + return retval; +} + +/* A line that contains a comma followed eventually by a number is + deemed to be the line in the form of <town, region postal-code>. */ + +static gboolean +e_address_western_is_postal (guchar *line) +{ + gboolean retval; + int cntr; + + if (strchr (line, ',') == NULL) + retval = FALSE; /* No comma. */ + else { + int index; + + /* Ensure that the first character after the comma is + a letter. */ + index = strcspn (line, ","); + index++; + while (isspace(line[index])) + index++; + + if (!isalpha (line[index])) + return FALSE; /* FIXME: ugly control flow. */ + + cntr = strlen(line) - 1; + + /* Go to the character immediately following the last + whitespace character. */ + while (cntr >= 0 && isspace(line[cntr])) + cntr--; + + while (cntr >= 0 && !isspace(line[cntr])) + cntr--; + + if (cntr == 0) + retval = FALSE; + else { + if (isdigit (line[cntr+1])) + retval = TRUE; + else + retval = FALSE; + } + } + + return retval; +} + +static gchar * +e_address_western_extract_po_box (gchar *line) +{ + /* Return everything from the beginning of the line to + the end of the first word that contains a number. */ + + int index; + + index = 0; + while (!isdigit(line[index])) + index++; + + while (isgraph(line[index])) + index++; + + return g_strndup (line, index); +} + +static gchar * +e_address_western_extract_locality (gchar *line) +{ + gint index; + + /* Everything before the comma is the locality. */ + index = strcspn(line, ","); + + if (index == 0) + return NULL; + else + return g_strndup (line, index); +} + + +/* Whatever resides between the comma and the start of the + postal code is deemed to be the region. */ + +static gchar * +e_address_western_extract_region (gchar *line) +{ + gint start, end; + + start = strcspn (line, ","); + start++; + while (isspace(line[start])) + start++; + + end = strlen(line) - 1; + while (isspace (line[end])) + end--; + + while (!isspace (line[end])) + end--; + + while (isspace (line[end])) + end--; + end++; + + /* Between start and end lie the string. */ + return g_strndup ( (line+start), end-start); +} + +static gchar * +e_address_western_extract_postal_code (gchar *line) +{ + int start, end; + + end = strlen (line) - 1; + while (isspace(line[end])) + end--; + + start = end; + end++; + + while (!isspace(line[start])) + start--; + start++; + + /* Between start and end lie the string. */ + return g_strndup ( (line+start), end-start); +} + + + +static void +e_address_western_extract_street (gchar *line, gchar **street, gchar **extended) +{ + const gchar *split = NULL; + gint cntr; + + for (cntr = 0; extended_keywords[cntr] != NULL; cntr++) { + split = e_strstrcase (line, extended_keywords[cntr]); + if (split != NULL) + break; + } + + if (split != NULL) { + *street = g_strndup (line, (split - line)); + *extended = g_strdup (split); + } + else { + *street = g_strdup (line); + *extended = NULL; + } + +} + + + +EAddressWestern * +e_address_western_parse (const gchar *in_address) +{ + gchar **lines; + gint linecntr, lineindex; + gchar *address; + gint cntr; + gboolean found_po_box, found_postal; + + EAddressWestern *eaw; +#if 0 + gint start, end; /* To be used to classify address lines. */ +#endif + + if (in_address == NULL) + return NULL; + + eaw = (EAddressWestern *)g_malloc (sizeof(EAddressWestern)); + eaw->po_box = NULL; + eaw->extended = NULL; + eaw->street = NULL; + eaw->locality = NULL; + eaw->region = NULL; + eaw->postal_code = NULL; + eaw->country = NULL; + + address = g_strndup (in_address, 2047); + + /* The first thing I'll do is divide the multiline input string + into lines. */ + + /* ... count the lines. */ + linecntr = 1; + lineindex = 0; + while (address[lineindex] != '\0') { + if (address[lineindex] == '\n') + linecntr++; + + lineindex++; + } + + /* ... tally them. */ + lines = (gchar **)g_malloc (sizeof(gchar *) * (linecntr+3)); + lineindex = 0; + lines[0] = &address[0]; + linecntr = 1; + while (address[lineindex] != '\0') { + if (address[lineindex] == '\n') { + lines[linecntr] = &address[lineindex + 1]; + linecntr++; + } + + lineindex++; + } + + /* Convert the newlines at the end of each line (except the last, + because it is already NULL terminated) to NULLs. */ + for (cntr = 0; cntr < (linecntr - 1); cntr++) { + *(strchr (lines[cntr], '\n')) = '\0'; + } + + e_address_western_remove_blank_lines (lines, &linecntr); + + /* Let's just test these functions. */ + found_po_box = FALSE; + found_postal = FALSE; + + for (cntr = 0; cntr < linecntr; cntr++) { + if (e_address_western_is_po_box (lines[cntr])) { + if (eaw->po_box == NULL) + eaw->po_box = e_address_western_extract_po_box (lines[cntr]); + found_po_box = TRUE; + } + else if (e_address_western_is_postal (lines[cntr])) { + if (eaw->locality == NULL) + eaw->locality = e_address_western_extract_locality (lines[cntr]); + if (eaw->region == NULL) + eaw->region = e_address_western_extract_region (lines[cntr]); + if (eaw->postal_code == NULL) + eaw->postal_code = e_address_western_extract_postal_code (lines[cntr]); + found_postal = TRUE; + } + else { + if (found_postal) { + if (eaw->country == NULL) + eaw->country = g_strdup (lines[cntr]); + else { + gchar *temp; + temp = g_strconcat (eaw->country, "\n", lines[cntr], NULL); + g_free (eaw->country); + eaw->country = temp; + } + } + else { + if (eaw->street == NULL) { + e_address_western_extract_street (lines[cntr], &eaw->street, + &eaw->extended ); + } + else { + gchar *temp; + temp = g_strdup_printf ( + "%s\n%s", + eaw->extended ? eaw->extended: "", + lines[cntr]); + g_free (eaw->extended); + eaw->extended = temp; + } + } + } + } + + g_free (lines); + g_free (address); + + return eaw; +} + + +void +e_address_western_free (EAddressWestern *eaw) +{ + if (eaw == NULL) + return; + + if (eaw->po_box != NULL) + g_free(eaw->po_box); + if (eaw->extended != NULL) + g_free(eaw->extended); + if (eaw->street != NULL) + g_free(eaw->street); + if (eaw->locality != NULL) + g_free(eaw->locality); + if (eaw->region != NULL) + g_free(eaw->region); + if (eaw->postal_code != NULL) + g_free(eaw->postal_code); + if (eaw->country != NULL) + g_free(eaw->country); + + g_free (eaw); +} + diff --git a/libedataserver/ename/e-address-western.h b/libedataserver/ename/e-address-western.h new file mode 100644 index 000000000..e6417f88c --- /dev/null +++ b/libedataserver/ename/e-address-western.h @@ -0,0 +1,21 @@ +#ifndef __E_ADDRESS_WESTERN_H__ +#define __E_ADDRESS_WESTERN_H__ + +typedef struct { + + /* Public */ + char *po_box; + char *extended; /* I'm not sure what this is. */ + char *street; + char *locality; /* For example, the city or town. */ + char *region; /* The state or province. */ + char *postal_code; + char *country; +} EAddressWestern; + +EAddressWestern *e_address_western_parse (const char *address); +void e_address_western_free (EAddressWestern *eaw); + +#endif /* ! __E_ADDRESS_WESTERN_H__ */ + + diff --git a/libedataserver/ename/e-name-western-tables.h b/libedataserver/ename/e-name-western-tables.h new file mode 100644 index 000000000..b5459049f --- /dev/null +++ b/libedataserver/ename/e-name-western-tables.h @@ -0,0 +1,74 @@ +#ifndef __E_NAME_WESTERN_TABLES_H__ +#define __E_NAME_WESTERN_TABLES_H__ + +char *e_name_western_pfx_table[] = { + + /* + * English. + */ + "mister", "miss.", "mr.", "mrs.", "ms.", + "miss", "mr", "mrs", "ms", "sir", + "professor", "prof.", "dr", "dr.", "doctor", + "judge", "justice", "chief justice", + "congressman", "congresswoman", "commander", + "lieutenant", "lt.", "colonel", "col.", "major", "maj.", + "general", "gen.", "admiral", "admr.", "sergeant", "sgt.", + "lord", "lady", "baron", "baroness", "duke", "duchess", + "king", "queen", "prince", "princess", + + "the most honorable", "the honorable", + "the reverend", "his holiness", + "his eminence", "his majesty", "her majesty", + "his grace", "her grace", + + "president", "vice president", "secretary", "undersecretary", + "consul", "ambassador", + + "senator", "saint", "st.", "pastor", "deacon", + "father", "bishop", "archbishop", "cardinal", "pope", + "reverend", "rev.", "rabbi", + + /* + * French. + */ + "monsieur", "m.", "mademoiselle", "melle", + "madame", "mme", "professeur", "dauphin", "dauphine", + + /* + * German + */ + "herr", "frau", "fraulein", "herr doktor", "doktor frau", "doktor frau doktor", + "frau doktor", + + + /* + * Spanish. + */ + "senor", "senora", "sra.", "senorita", "srita.", + + NULL}; + +char *e_name_western_sfx_table[] = { + + /* + * English. + */ + "junior", "senior", "jr", "sr", "I", "II", "III", "IV", "V", + "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", + "XV", "XVI", "XVII", "XVIII", "XIX", "XX", "XXI", "XXII", + "phd", "ms", "md", "esq", "esq.", "esquire", + + NULL}; + +char *e_name_western_twopart_sfx_table[] = { + + /* + * English. + */ + "the first", "the second", "the third", + + NULL}; + +char *e_name_western_complex_last_table[] = {"van", "von", "de", "di", NULL}; + +#endif /* ! __E_NAME_WESTERN_TABLES_H__ */ diff --git a/libedataserver/ename/e-name-western.c b/libedataserver/ename/e-name-western.c new file mode 100644 index 000000000..b6802c433 --- /dev/null +++ b/libedataserver/ename/e-name-western.c @@ -0,0 +1,982 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * A simple Western name parser. + * + * <Nat> Jamie, do you know anything about name parsing? + * <jwz> Are you going down that rat hole? Bring a flashlight. + * + * Authors: + * Nat Friedman <nat@ximian.com> + * + * Copyright 1999 - 2001, Ximian, Inc. + */ + +#include <ctype.h> +#include <string.h> +#include <glib.h> + +#include <ename/e-name-western.h> +#include <ename/e-name-western-tables.h> + +typedef struct { + int prefix_idx; + int first_idx; + int middle_idx; + int nick_idx; + int last_idx; + int suffix_idx; +} ENameWesternIdxs; + +static int +e_name_western_str_count_words (char *str) +{ + int word_count; + char *p; + + word_count = 0; + + for (p = str; p != NULL; p = g_utf8_strchr (p, -1, ' ')) { + word_count ++; + p = g_utf8_next_char (p); + } + + return word_count; +} + +static void +e_name_western_cleanup_string (char **str) +{ + char *newstr; + char *p; + + if (*str == NULL) + return; + + /* skip any spaces and commas at the start of the string */ + p = *str; + while (g_unichar_isspace (g_utf8_get_char(p)) || *p == ',') + p = g_utf8_next_char (p); + + /* make the copy we're going to return */ + newstr = g_strdup (p); + + if ( strlen(newstr) > 0) { + /* now search from the back, skipping over any spaces and commas */ + p = newstr + strlen (newstr); + p = g_utf8_prev_char (p); + while (g_unichar_isspace (g_utf8_get_char(p)) || *p == ',') + p = g_utf8_prev_char (p); + /* advance p to after the character that caused us to exit the + previous loop, and end the string. */ + if ((! g_unichar_isspace (g_utf8_get_char (p))) && *p != ',') + p = g_utf8_next_char (p); + *p = '\0'; + } + + g_free (*str); + *str = newstr; +} + +static char * +e_name_western_get_words_at_idx (char *str, int idx, int num_words) +{ + GString *words; + char *p; + int word_count; + + /* + * Walk to the end of the words. + */ + words = g_string_new (""); + word_count = 0; + p = str + idx; + while (word_count < num_words && *p != '\0') { + while (! g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') { + words = g_string_append_unichar (words, g_utf8_get_char (p)); + p = g_utf8_next_char (p); + } + + while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') + p = g_utf8_next_char (p); + + word_count ++; + } + + return g_string_free (words, FALSE); +} + +/* + * What the fuck is wrong with glib's MAX macro. + */ +static int +e_name_western_max (const int a, const int b) +{ + if (a > b) + return a; + + return b; +} + +static gboolean +e_name_western_word_is_suffix (char *word) +{ + int i; + + for (i = 0; e_name_western_sfx_table [i] != NULL; i ++) { + int length = strlen (e_name_western_sfx_table [i]); + if (!g_strcasecmp (word, e_name_western_sfx_table [i]) || + ( !g_strncasecmp (word, e_name_western_sfx_table [i], length) && + strlen(word) == length + 1 && + word[length] == '.' )) + return TRUE; + } + + return FALSE; +} + +static char * +e_name_western_get_one_prefix_at_str (char *str) +{ + char *word; + int i; + + /* + * Check for prefixes from our table. + */ + for (i = 0; e_name_western_pfx_table [i] != NULL; i ++) { + int pfx_words; + char *words; + + pfx_words = e_name_western_str_count_words (e_name_western_pfx_table [i]); + words = e_name_western_get_words_at_idx (str, 0, pfx_words); + + if (! g_strcasecmp (words, e_name_western_pfx_table [i])) + return words; + + g_free (words); + } + + /* + * Check for prefixes we don't know about. These are always a + * sequence of more than one letters followed by a period. + */ + word = e_name_western_get_words_at_idx (str, 0, 1); + + if (g_utf8_strlen (word, -1) > 2 && + g_unichar_isalpha (g_utf8_get_char (word)) && + g_unichar_isalpha (g_utf8_get_char (g_utf8_next_char (word))) && + word [strlen (word) - 1] == '.') + return word; + + g_free (word); + + return NULL; +} + +static char * +e_name_western_get_prefix_at_str (char *str) +{ + char *pfx; + char *pfx1; + char *pfx2; + char *p; + + /* Get the first prefix. */ + pfx1 = e_name_western_get_one_prefix_at_str (str); + + if (pfx1 == NULL) + return NULL; + + /* Check for a second prefix. */ + p = str + strlen (pfx1); + while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') + p = g_utf8_next_char (p); + + pfx2 = e_name_western_get_one_prefix_at_str (p); + + if (pfx2 != NULL) { + int pfx_len; + + pfx_len = (p + strlen (pfx2)) - str; + pfx = g_malloc0 (pfx_len + 1); + strncpy (pfx, str, pfx_len); + } else { + pfx = g_strdup (pfx1); + } + + g_free (pfx1); + g_free (pfx2); + + return pfx; +} + +static void +e_name_western_extract_prefix (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *pfx; + + pfx = e_name_western_get_prefix_at_str (name->full); + + if (pfx == NULL) + return; + + idxs->prefix_idx = 0; + name->prefix = pfx; +} + +static gboolean +e_name_western_is_complex_last_beginning (char *word) +{ + int i; + + for (i = 0; e_name_western_complex_last_table [i] != NULL; i ++) { + + if (! g_strcasecmp ( + word, e_name_western_complex_last_table [i])) + return TRUE; + } + + return FALSE; +} + +static void +e_name_western_extract_first (ENameWestern *name, ENameWesternIdxs *idxs) +{ + /* + * If there's a prefix, then the first name is right after it. + */ + if (idxs->prefix_idx != -1) { + int first_idx; + char *p; + + first_idx = idxs->prefix_idx + strlen (name->prefix); + + /* Skip past white space. */ + p = name->full + first_idx; + while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') + p = g_utf8_next_char (p); + + if (*p == '\0') + return; + + idxs->first_idx = p - name->full; + name->first = e_name_western_get_words_at_idx ( + name->full, idxs->first_idx, 1); + + } else { + + /* + * Otherwise, the first name is probably the first string. + */ + idxs->first_idx = 0; + name->first = e_name_western_get_words_at_idx ( + name->full, idxs->first_idx, 1); + } + + /* + * Check that we didn't just assign the beginning of a + * compound last name to the first name. + */ + if (name->first != NULL) { + if (e_name_western_is_complex_last_beginning (name->first)) { + g_free (name->first); + name->first = NULL; + idxs->first_idx = -1; + } + } +} + +static void +e_name_western_extract_middle (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *word; + char *middle; + + /* + * Middle names can only exist if you have a first name. + */ + if (idxs->first_idx == -1) + return; + + middle = name->full + idxs->first_idx + strlen (name->first); + if (*middle == '\0') + return; + + middle = g_utf8_next_char (middle); + if (*middle == '\0') + return; + + /* + * Search for the first space (or the terminating \0) + */ + while (g_unichar_isspace (g_utf8_get_char (middle)) && + *middle != '\0') + middle = g_utf8_next_char (middle); + + if (*middle == '\0') + return; + + /* + * Skip past the nickname, if it's there. + */ + if (*middle == '\"') { + if (idxs->nick_idx == -1) + return; + + middle = name->full + idxs->nick_idx + strlen (name->nick); + middle = g_utf8_next_char (middle); + + while (g_unichar_isspace (g_utf8_get_char (middle)) && + *middle != '\0') + middle = g_utf8_next_char (middle); + + if (*middle == '\0') + return; + } + + /* + * Make sure this isn't the beginning of a complex last name. + */ + word = e_name_western_get_words_at_idx (name->full, middle - name->full, 1); + if (e_name_western_is_complex_last_beginning (word)) { + g_free (word); + return; + } + + /* + * Make sure this isn't a suffix. + */ + e_name_western_cleanup_string (& word); + if (e_name_western_word_is_suffix (word)) { + g_free (word); + return; + } + + /* + * Make sure we didn't just grab a cute nickname. + */ + if (word [0] == '\"') { + g_free (word); + return; + } + + idxs->middle_idx = middle - name->full; + name->middle = word; +} + +static void +e_name_western_extract_nickname (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *nick; + int start_idx; + GString *str; + + if (idxs->first_idx == -1) + return; + + if (idxs->middle_idx > idxs->first_idx) + nick = name->full + idxs->middle_idx + strlen (name->middle); + else + nick = name->full + idxs->first_idx + strlen (name->first); + + while (*nick != '\"' && *nick != '\0') + nick = g_utf8_next_char (nick); + + if (*nick != '\"') + return; + + start_idx = nick - name->full; + + /* + * Advance to the next double quote. + */ + str = g_string_new ("\""); + nick = g_utf8_next_char (nick); + + while (*nick != '\"' && *nick != '\0') { + str = g_string_append_unichar (str, g_utf8_get_char (nick)); + nick = g_utf8_next_char (nick); + } + + if (*nick == '\0') { + g_string_free (str, TRUE); + return; + } + str = g_string_append (str, "\""); + + name->nick = g_string_free (str, FALSE); + + idxs->nick_idx = start_idx; +} + +static int +e_name_western_last_get_max_idx (ENameWestern *name, ENameWesternIdxs *idxs) +{ + int max_idx = -1; + + if (name->prefix != NULL) + max_idx = e_name_western_max ( + max_idx, idxs->prefix_idx + strlen (name->prefix)); + + if (name->first != NULL) + max_idx = e_name_western_max ( + max_idx, idxs->first_idx + strlen (name->first)); + + if (name->middle != NULL) + max_idx = e_name_western_max ( + max_idx, idxs->middle_idx + strlen (name->middle)); + + if (name->nick != NULL) + max_idx = e_name_western_max ( + max_idx, idxs->nick_idx + strlen (name->nick)); + + return max_idx; +} + +static void +e_name_western_extract_last (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *word; + int idx = -1; + char *last; + + idx = e_name_western_last_get_max_idx (name, idxs); + + /* + * In the case where there is no preceding name element, the + * name is either just a first name ("Nat", "John"), is a + * single-element name ("Cher", which we treat as a first + * name), or is just a last name. The only time we can + * differentiate a last name alone from a single-element name + * or a first name alone is if it's a complex last name ("de + * Icaza", "van Josephsen"). So if there is no preceding name + * element, we check to see whether or not the first part of + * the name is the beginning of a complex name. If it is, + * we subsume the entire string. If we accidentally subsume + * the suffix, this will get fixed in the fixup routine. + */ + if (idx == -1) { + word = e_name_western_get_words_at_idx (name->full, 0, 1); + if (! e_name_western_is_complex_last_beginning (word)) { + g_free (word); + return; + } + + name->last = g_strdup (name->full); + idxs->last_idx = 0; + return; + } + + last = name->full + idx; + + /* Skip past the white space. */ + while (g_unichar_isspace (g_utf8_get_char (last)) && *last != '\0') + last = g_utf8_next_char (last); + + if (*last == '\0') + return; + + word = e_name_western_get_words_at_idx (name->full, last - name->full, 1); + e_name_western_cleanup_string (& word); + if (e_name_western_word_is_suffix (word)) { + g_free (word); + return; + } + g_free (word); + + /* + * Subsume the rest of the string into the last name. If we + * accidentally include the prefix, it will get fixed later. + * This is the only way to handle things like "Miguel de Icaza + * Amozorrutia" without dropping data and forcing the user + * to retype it. + */ + name->last = g_strdup (last); + idxs->last_idx = last - name->full; +} + +static char * +e_name_western_get_preceding_word (char *str, int idx) +{ + int word_len; + char *word; + char *p; + + p = str + idx; + + while (g_unichar_isspace (g_utf8_get_char (p)) && p > str) + p = g_utf8_prev_char (p); + + while (! g_unichar_isspace (g_utf8_get_char (p)) && p > str) + p = g_utf8_prev_char (p); + + if (g_unichar_isspace (g_utf8_get_char (p))) + p = g_utf8_next_char (p); + + word_len = (str + idx) - p; + word = g_malloc0 (word_len + 1); + if (word_len > 0) + strncpy (word, p, word_len); + + return word; +} + +static char * +e_name_western_get_suffix_at_str_end (char *str) +{ + char *suffix; + char *p; + + /* + * Walk backwards till we reach the beginning of the + * (potentially-comma-separated) list of suffixes. + */ + p = str + strlen (str); + while (1) { + char *nextp; + char *word; + + word = e_name_western_get_preceding_word (str, p - str); + nextp = p - strlen (word); + if (nextp == str) { + g_free (word); + break; + } + nextp = g_utf8_prev_char (nextp); + + e_name_western_cleanup_string (& word); + + if (e_name_western_word_is_suffix (word)) { + p = nextp; + g_free (word); + } else { + g_free (word); + break; + } + } + + if (p == (str + strlen (str))) + return NULL; + + suffix = g_strdup (p); + e_name_western_cleanup_string (& suffix); + + if (strlen (suffix) == 0) { + g_free (suffix); + return NULL; + } + + return suffix; +} + +static void +e_name_western_extract_suffix (ENameWestern *name, ENameWesternIdxs *idxs) +{ + name->suffix = e_name_western_get_suffix_at_str_end (name->full); + + if (name->suffix == NULL) + return; + + idxs->suffix_idx = strlen (name->full) - strlen (name->suffix); +} + +static gboolean +e_name_western_detect_backwards (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *comma; + char *word; + + comma = g_utf8_strchr (name->full, -1, ','); + + if (comma == NULL) + return FALSE; + + /* + * If there's a comma, we need to detect whether it's + * separating the last name from the first or just separating + * suffixes. So we grab the word which comes before the + * comma and check if it's a suffix. + */ + word = e_name_western_get_preceding_word (name->full, comma - name->full); + + if (e_name_western_word_is_suffix (word)) { + g_free (word); + return FALSE; + } + + g_free (word); + return TRUE; +} + +static void +e_name_western_reorder_asshole (ENameWestern *name, ENameWesternIdxs *idxs) +{ + char *prefix; + char *last; + char *suffix; + char *firstmidnick; + char *newfull; + + char *comma; + char *p; + + if (! e_name_western_detect_backwards (name, idxs)) + return; + + /* + * Convert + * <Prefix> <Last name>, <First name> <Middle[+nick] name> <Suffix> + * to + * <Prefix> <First name> <Middle[+nick] name> <Last name> <Suffix> + */ + + /* + * Grab the prefix from the beginning. + */ + prefix = e_name_western_get_prefix_at_str (name->full); + + /* + * Everything from the end of the prefix to the comma is the + * last name. + */ + comma = g_utf8_strchr (name->full, -1, ','); + if (comma == NULL) + return; + + p = name->full + (prefix == NULL ? 0 : strlen (prefix)); + + while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') + p = g_utf8_next_char (p); + + last = g_malloc0 (comma - p + 1); + strncpy (last, p, comma - p); + + /* + * Get the suffix off the end. + */ + suffix = e_name_western_get_suffix_at_str_end (name->full); + + /* + * Firstmidnick is everything from the comma to the beginning + * of the suffix. + */ + p = g_utf8_next_char (comma); + + while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') + p = g_utf8_next_char (p); + + if (suffix != NULL) { + char *q; + + /* + * Point q at the beginning of the suffix. + */ + q = name->full + strlen (name->full) - strlen (suffix); + q = g_utf8_prev_char (q); + + /* + * Walk backwards until we hit the space which + * separates the suffix from firstmidnick. + */ + while (! g_unichar_isspace (g_utf8_get_char (q)) && q > comma) + q = g_utf8_prev_char (q); + + if ((q - p + 1) > 0) { + firstmidnick = g_malloc0 (q - p + 1); + strncpy (firstmidnick, p, q - p); + } else + firstmidnick = NULL; + } else { + firstmidnick = g_strdup (p); + } + + /* + * Create our new reordered version of the name. + */ +#define NULLSTR(a) ((a) == NULL ? "" : (a)) + newfull = g_strdup_printf ("%s %s %s %s", NULLSTR (prefix), NULLSTR (firstmidnick), + NULLSTR (last), NULLSTR (suffix)); + g_strstrip (newfull); + g_free (name->full); + name->full = newfull; + + + g_free (prefix); + g_free (firstmidnick); + g_free (last); + g_free (suffix); +} + +static void +e_name_western_zap_nil (char **str, int *idx) +{ + if (*str == NULL) + return; + + if (strlen (*str) != 0) + return; + + *idx = -1; + g_free (*str); + *str = NULL; +} + +#define FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION \ + char *last_start = NULL; \ + if (name->last) \ + last_start = g_utf8_strchr (name->last, -1, ' '); \ + if (last_start) { \ + char *new_last, *new_first; \ + \ + new_last = g_strdup (g_utf8_next_char (last_start)); \ + *last_start = '\0'; \ + \ + idxs->last_idx += (last_start - name->last) + 1; \ + \ + new_first = g_strdup_printf ("%s %s %s", \ + name->first, \ + name->middle, \ + name->last); \ + \ + g_free (name->first); \ + g_free (name->middle); \ + g_free (name->last); \ + \ + name->first = new_first; \ + name->middle = NULL; \ + name->last = new_last; \ + \ + idxs->middle_idx = -1; \ + } else { \ + char *new_first; \ + \ + new_first = g_strdup_printf ("%s %s %s", \ + name->first, \ + name->middle, \ + name->last); \ + \ + g_free (name->first); \ + g_free (name->middle); \ + g_free (name->last); \ + \ + name->first = new_first; \ + name->middle = NULL; \ + name->last = NULL; \ + idxs->middle_idx = -1; \ + idxs->last_idx = -1; \ + } + +#define CHECK_MIDDLE_NAME_FOR_CONJUNCTION(conj) \ + if (idxs->middle_idx != -1 && !strcmp (name->middle, conj)) { \ + FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION \ + } + +#define CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE(conj) \ + if (idxs->middle_idx != -1 && !strcasecmp (name->middle, conj)) { \ + FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION \ + } + +static void +e_name_western_fixup (ENameWestern *name, ENameWesternIdxs *idxs) +{ + /* + * The middle and last names cannot be the same. + */ + if (idxs->middle_idx != -1 && idxs->middle_idx == idxs->last_idx) { + idxs->middle_idx = -1; + g_free (name->middle); + name->middle = NULL; + } + + /* + * If we have a middle name and no last name, then we mistook + * the last name for the middle name. + */ + if (idxs->last_idx == -1 && idxs->middle_idx != -1) { + idxs->last_idx = idxs->middle_idx; + name->last = name->middle; + name->middle = NULL; + idxs->middle_idx = -1; + } + + /* + * Check to see if we accidentally included the suffix in the + * last name. + */ + if (idxs->suffix_idx != -1 && idxs->last_idx != -1 && + idxs->suffix_idx < (idxs->last_idx + strlen (name->last))) { + char *sfx; + + sfx = name->last + (idxs->suffix_idx - idxs->last_idx); + if (sfx != NULL) { + char *newlast; + char *p; + + p = sfx; + p = g_utf8_prev_char (p); + while (g_unichar_isspace (g_utf8_get_char (p)) && p > name->last) + p = g_utf8_prev_char (p); + p = g_utf8_next_char (p); + + newlast = g_malloc0 (p - name->last + 1); + strncpy (newlast, name->last, p - name->last); + g_free (name->last); + name->last = newlast; + } + } + + /* + * If we have a prefix and a first name, but no last name, + * then we need to assign the first name to the last name. + * This way we get things like "Mr Friedman" correctly. + */ + if (idxs->first_idx != -1 && idxs->prefix_idx != -1 && + idxs->last_idx == -1) { + name->last = name->first; + idxs->last_idx = idxs->first_idx; + idxs->first_idx = -1; + name->first = NULL; + } + + if (idxs->middle_idx != -1) { + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("&"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("*"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("|"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("^"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("&&"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("||"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("+"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("-"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("and"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("or"); + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("plus"); + + /* Spanish */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("y"); + + /* German */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("und"); + + /* Italian */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("e"); + + /* Czech */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("a"); + + /* Finnish */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("ja"); + + /* French */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("et"); + + /* Russian */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("\xd0\x98"); /* u+0418 */ + CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("\xd0\xb8"); /* u+0438 */ + } + + /* + * Remove stray spaces and commas (although there don't seem + * to be any in the test cases, they might show up later). + */ + e_name_western_cleanup_string (& name->prefix); + e_name_western_cleanup_string (& name->first); + e_name_western_cleanup_string (& name->middle); + e_name_western_cleanup_string (& name->nick); + e_name_western_cleanup_string (& name->last); + e_name_western_cleanup_string (& name->suffix); + + /* + * Make zero-length strings just NULL. + */ + e_name_western_zap_nil (& name->prefix, & idxs->prefix_idx); + e_name_western_zap_nil (& name->first, & idxs->first_idx); + e_name_western_zap_nil (& name->middle, & idxs->middle_idx); + e_name_western_zap_nil (& name->nick, & idxs->nick_idx); + e_name_western_zap_nil (& name->last, & idxs->last_idx); + e_name_western_zap_nil (& name->suffix, & idxs->suffix_idx); +} + +/** + * e_name_western_western_parse_fullname: + * @full_name: A string containing a Western name. + * + * Parses @full_name and returns an #ENameWestern object filled with + * the component parts of the name. + */ +ENameWestern * +e_name_western_parse (const char *full_name) +{ + ENameWesternIdxs *idxs; + ENameWestern *wname; + char *end; + + if (!g_utf8_validate (full_name, -1, (const char **)&end)) { + g_warning ("e_name_western_parse passed invalid UTF-8 sequence"); + *end = '\0'; + } + + wname = g_new0 (ENameWestern, 1); + + wname->full = g_strdup (full_name); + + idxs = g_new0 (ENameWesternIdxs, 1); + + idxs->prefix_idx = -1; + idxs->first_idx = -1; + idxs->middle_idx = -1; + idxs->nick_idx = -1; + idxs->last_idx = -1; + idxs->suffix_idx = -1; + + /* + * An extremely simple algorithm. + * + * The goal here is to get it right 95% of the time for + * Western names. + * + * First we check to see if this is an ass-backwards name + * ("Prefix Last, First Middle Suffix"). These names really + * suck (imagine "Dr von Johnson, Albert Roderick Jr"), so + * we reorder them first and then parse them. + * + * Next, we grab the most obvious assignments for the various + * parts of the name. Once this is done, we check for stupid + * errors and fix them up. + */ + e_name_western_reorder_asshole (wname, idxs); + + e_name_western_extract_prefix (wname, idxs); + e_name_western_extract_first (wname, idxs); + e_name_western_extract_nickname (wname, idxs); + e_name_western_extract_middle (wname, idxs); + e_name_western_extract_last (wname, idxs); + e_name_western_extract_suffix (wname, idxs); + + e_name_western_fixup (wname, idxs); + + g_free (idxs); + + return wname; +} + +/** + * e_name_western_free: + * @name: An ENameWestern object which needs to be freed. + * + * Deep-frees @name + */ +void +e_name_western_free (ENameWestern *w) +{ + + g_free (w->prefix); + g_free (w->first); + g_free (w->middle); + g_free (w->nick); + g_free (w->last); + g_free (w->suffix); + + g_free (w->full); + + g_free (w); +} diff --git a/libedataserver/ename/e-name-western.h b/libedataserver/ename/e-name-western.h new file mode 100644 index 000000000..fa5bac494 --- /dev/null +++ b/libedataserver/ename/e-name-western.h @@ -0,0 +1,21 @@ +#ifndef __E_NAME_WESTERN_H__ +#define __E_NAME_WESTERN_H__ + +typedef struct { + + /* Public */ + char *prefix; + char *first; + char *middle; + char *nick; + char *last; + char *suffix; + + /* Private */ + char *full; +} ENameWestern; + +ENameWestern *e_name_western_parse (const char *full_name); +void e_name_western_free (ENameWestern *w); + +#endif /* ! __E_NAME_WESTERN_H__ */ diff --git a/libedataserver/libedataserver-1.0.pc.in b/libedataserver/libedataserver-1.0.pc.in new file mode 100644 index 000000000..bcf4f87f9 --- /dev/null +++ b/libedataserver/libedataserver-1.0.pc.in @@ -0,0 +1,14 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +idldir=@idldir@ +IDL_INCLUDES=-I ${idldir} @IDL_INCLUDES@ + +Name: libedataserver +Description: Utily library for evolution data servers +Version: @VERSION@ +Requires: libbonobo-2.0 libgnome-2.0 gal-2.2 >= @GAL_REQUIRED@ +Libs: -L${libdir} -ledataserver +Cflags: -I${includedir}/evolution-data-server-1.0 diff --git a/libedataserver/md5-utils.c b/libedataserver/md5-utils.c new file mode 100644 index 000000000..6f7e06f48 --- /dev/null +++ b/libedataserver/md5-utils.c @@ -0,0 +1,363 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to md5_init, call md5_update as + * needed on buffers full of bytes, and then call md5_Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + + +#include <stdio.h> +#include <string.h> +#include "md5-utils.h" + + +static void md5_transform (guint32 buf[4], const guint32 in[16]); + +static gint _ie = 0x44332211; +static union _endian { gint i; gchar b[4]; } *_endian = (union _endian *)&_ie; +#define IS_BIG_ENDIAN() (_endian->b[0] == '\x44') +#define IS_LITTLE_ENDIAN() (_endian->b[0] == '\x11') + + +/* + * Note: this code is harmless on little-endian machines. + */ +static void +_byte_reverse (guchar *buf, guint32 longs) +{ + guint32 t; + do { + t = (guint32) ((guint32) buf[3] << 8 | buf[2]) << 16 | + ((guint32) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } while (--longs); +} + +/** + * md5_init: Initialise an md5 context object + * @ctx: md5 context + * + * Initialise an md5 buffer. + * + **/ +void +md5_init (MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + if (IS_BIG_ENDIAN()) + ctx->doByteReverse = 1; + else + ctx->doByteReverse = 0; +} + + + +/** + * md5_update: add a buffer to md5 hash computation + * @ctx: conetxt object used for md5 computaion + * @buf: buffer to add + * @len: buffer length + * + * Update context to reflect the concatenation of another buffer full + * of bytes. Use this to progressively construct an md5 hash. + **/ +void +md5_update (MD5Context *ctx, const guchar *buf, guint32 len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + guchar *p = (guchar *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy (ctx->in, buf, len); +} + + + + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +/** + * md5_final: copy the final md5 hash to a bufer + * @digest: 16 bytes buffer + * @ctx: context containing the calculated md5 + * + * copy the final md5 hash to a bufer + **/ +void +md5_final (MD5Context *ctx, guchar digest[16]) +{ + guint32 count; + guchar *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset (ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + md5_transform (ctx->buf, (guint32 *) ctx->in); + if (ctx->doByteReverse) + _byte_reverse ((guchar *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); +} + + + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. md5_Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +md5_transform (guint32 buf[4], const guint32 in[16]) +{ + register guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + + +/** + * md5_get_digest: get the md5 hash of a buffer + * @buffer: byte buffer + * @buffer_size: buffer size (in bytes) + * @digest: 16 bytes buffer receiving the hash code. + * + * Get the md5 hash of a buffer. The result is put in + * the 16 bytes buffer @digest . + **/ +void +md5_get_digest (const gchar *buffer, gint buffer_size, guchar digest[16]) +{ + MD5Context ctx; + + md5_init (&ctx); + md5_update (&ctx, buffer, buffer_size); + md5_final (&ctx, digest); + +} + + +/** + * md5_get_digest_from_file: get the md5 hash of a file + * @filename: file name + * @digest: 16 bytes buffer receiving the hash code. + * + * Get the md5 hash of a file. The result is put in + * the 16 bytes buffer @digest . + **/ +void +md5_get_digest_from_file (const gchar *filename, guchar digest[16]) +{ + MD5Context ctx; + guchar tmp_buf[1024]; + gint nb_bytes_read; + FILE *fp; + + printf("generating checksum\n"); + + md5_init (&ctx); + fp = fopen(filename, "r"); + if (!fp) { + return; + } + + while ((nb_bytes_read = fread (tmp_buf, sizeof (guchar), 1024, fp)) > 0) + md5_update (&ctx, tmp_buf, nb_bytes_read); + + if (ferror(fp)) { + fclose(fp); + return; + } + + + md5_final (&ctx, digest); + + printf("checksum done\n"); +} + + + + diff --git a/libedataserver/md5-utils.h b/libedataserver/md5-utils.h new file mode 100644 index 000000000..607471a75 --- /dev/null +++ b/libedataserver/md5-utils.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to rpmMD5Init, call rpmMD5Update as + * needed on buffers full of bytes, and then call rpmMD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + + +#ifndef MD5_UTILS_H +#define MD5_UTILS_H + +#include <glib.h> + + +typedef struct _MD5Context { + guint32 buf[4]; + guint32 bits[2]; + guchar in[64]; + gint doByteReverse; +} MD5Context; + + +void md5_get_digest (const gchar *buffer, gint buffer_size, guchar digest[16]); + +/* use this one when speed is needed */ +/* for use in provider code only */ +void md5_get_digest_from_file (const gchar *filename, guchar digest[16]); + +/* raw routines */ +void md5_init (MD5Context *ctx); +void md5_update (MD5Context *ctx, const guchar *buf, guint32 len); +void md5_final (MD5Context *ctx, guchar digest[16]); + + +#endif /* MD5_UTILS_H */ |