aboutsummaryrefslogtreecommitdiffstats
path: root/addressbook/backend
diff options
context:
space:
mode:
authorChris Toshok <toshok@ximian.com>2002-06-30 16:18:44 +0800
committerChris Toshok <toshok@src.gnome.org>2002-06-30 16:18:44 +0800
commit987eeb288e6773997f38178747532d894d890d85 (patch)
treefb7cb27ca212e9e0a920ec4f093f66e64e882058 /addressbook/backend
parent6256cb6fe996561bdfdc2e954279eb6ecd3f53d9 (diff)
downloadgsoc2013-evolution-987eeb288e6773997f38178747532d894d890d85.tar.gz
gsoc2013-evolution-987eeb288e6773997f38178747532d894d890d85.tar.zst
gsoc2013-evolution-987eeb288e6773997f38178747532d894d890d85.zip
add pas-backend-summary.[ch].
2002-06-29 Chris Toshok <toshok@ximian.com> * backend/pas/Makefile.am (libpas_a_SOURCES): add pas-backend-summary.[ch]. * backend/pas/pas-backend-file.c (string_to_dbt): move this to the top of the file so it can be used in.. (build_summary): loop over the db, adding cards ot the summary. (do_summary_query): call pas_backend_summary_search and loop over the returned id's looking them up in the db. (pas_backend_file_search): call pas_backend_summary_is_summary_query, and either call do_summary_query if it's a query over just the set of attributes in the summary or use the old, slow method if not. (pas_backend_file_process_create_card): call pas_backend_summary_add_card. (pas_backend_file_process_remove_card): call pas_backend_summary_remove_card. (pas_backend_file_process_modify_card): call remove_card/add_card. (pas_backend_file_load_uri): try to load the summary file, and if it doesn't exist create it. (pas_backend_file_destroy): unref the summary. * backend/pas/pas-backend-summary.[ch]: new files, reading and writing (and querying) summaries. svn path=/trunk/; revision=17330
Diffstat (limited to 'addressbook/backend')
-rw-r--r--addressbook/backend/pas/Makefile.am2
-rw-r--r--addressbook/backend/pas/pas-backend-file.c246
-rw-r--r--addressbook/backend/pas/pas-backend-summary.c957
-rw-r--r--addressbook/backend/pas/pas-backend-summary.h63
4 files changed, 1200 insertions, 68 deletions
diff --git a/addressbook/backend/pas/Makefile.am b/addressbook/backend/pas/Makefile.am
index 3f9a2627cd..237a11b83a 100644
--- a/addressbook/backend/pas/Makefile.am
+++ b/addressbook/backend/pas/Makefile.am
@@ -50,6 +50,8 @@ libpas_a_SOURCES = \
$(LDAP_BACKEND) \
pas-backend.c \
pas-backend.h \
+ pas-backend-summary.c \
+ pas-backend-summary.h \
pas-card-cursor.c \
pas-card-cursor.h
diff --git a/addressbook/backend/pas/pas-backend-file.c b/addressbook/backend/pas/pas-backend-file.c
index e80a0a41dd..a6216cf98e 100644
--- a/addressbook/backend/pas/pas-backend-file.c
+++ b/addressbook/backend/pas/pas-backend-file.c
@@ -38,11 +38,13 @@
#include "pas-book.h"
#include "pas-card-cursor.h"
#include "pas-backend-card-sexp.h"
+#include "pas-backend-summary.h"
#define PAS_BACKEND_FILE_VERSION_NAME "PAS-DB-VERSION"
#define PAS_BACKEND_FILE_VERSION "0.2"
#define PAS_ID_PREFIX "pas-id-"
+#define SUMMARY_FLUSH_TIMEOUT 5000
static PASBackendClass *pas_backend_file_parent_class;
typedef struct _PASBackendFileCursorPrivate PASBackendFileCursorPrivate;
@@ -59,6 +61,7 @@ struct _PASBackendFilePrivate {
EList *book_views;
gboolean writable;
GHashTable *address_lists;
+ PASBackendSummary *summary;
};
struct _PASBackendFileCursorPrivate {
@@ -87,6 +90,100 @@ struct _PasBackendFileChangeContext {
GList *del_ids;
};
+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
+build_summary (PASBackendFilePrivate *bfpriv)
+{
+ DB *db = bfpriv->file_db;
+ DBC *dbc;
+ int db_error;
+ DBT id_dbt, vcard_dbt;
+
+ db_error = db->cursor (db, NULL, &dbc, 0);
+
+ if (db_error != 0) {
+ g_warning ("pas_backend_file_build_all_cards_list: error building list\n");
+ }
+
+ memset (&vcard_dbt, 0, sizeof (vcard_dbt));
+ memset (&id_dbt, 0, sizeof (id_dbt));
+ db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
+
+ while (db_error == 0) {
+
+ /* don't include the version in the list of cards */
+ if (id_dbt.size != strlen(PAS_BACKEND_FILE_VERSION_NAME) + 1
+ || strcmp (id_dbt.data, PAS_BACKEND_FILE_VERSION_NAME)) {
+
+ pas_backend_summary_add_card (bfpriv->summary, vcard_dbt.data);
+ }
+
+ db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
+
+ }
+}
+
+static void
+do_summary_query (PASBackendFile *bf,
+ PASBackendFileBookView *view)
+{
+ GPtrArray *ids = pas_backend_summary_search (bf->priv->summary, view->search);
+ int db_error = 0;
+ GList *cards = NULL;
+ gint card_count = 0, card_threshold = 20, card_threshold_max = 3000;
+ DB *db = bf->priv->file_db;
+ DBT id_dbt, vcard_dbt;
+ int i;
+
+ for (i = 0; i < ids->len; i ++) {
+ char *id = g_ptr_array_index (ids, i);
+
+ string_to_dbt (id, &id_dbt);
+ memset (&vcard_dbt, 0, sizeof (vcard_dbt));
+
+ db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
+
+ if (db_error == 0) {
+ cards = g_list_prepend (cards, g_strdup (vcard_dbt.data));
+ card_count ++;
+
+ /* If we've accumulated a number of checks, pass them off to the client. */
+ if (card_count >= card_threshold) {
+ pas_book_view_notify_add (view->book_view, cards);
+ /* Clean up the handed-off data. */
+ g_list_foreach (cards, (GFunc)g_free, NULL);
+ g_list_free (cards);
+ cards = NULL;
+ card_count = 0;
+
+ /* Yeah, this scheme is overly complicated. But I like it. */
+ if (card_threshold < card_threshold_max) {
+ card_threshold = MIN (2*card_threshold, card_threshold_max);
+ }
+ }
+ }
+ else
+ continue; /* XXX */
+ }
+
+ g_ptr_array_free (ids, TRUE);
+
+ if (card_count)
+ pas_book_view_notify_add (view->book_view, cards);
+
+ pas_book_view_notify_complete (view->book_view);
+
+ g_list_foreach (cards, (GFunc)g_free, NULL);
+ g_list_free (cards);
+}
+
static PASBackendFileBookView *
pas_backend_file_book_view_copy(const PASBackendFileBookView *book_view, void *closure)
{
@@ -206,14 +303,6 @@ view_destroy(GtkObject *object, gpointer data)
bonobo_object_unref(BONOBO_OBJECT(book));
}
-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 char *
pas_backend_file_create_unique_id (char *vcard)
{
@@ -239,13 +328,6 @@ pas_backend_file_search (PASBackendFile *bf,
PASBook *book,
const PASBackendFileBookView *cnstview)
{
- int db_error = 0;
- GList *cards = NULL;
- gint card_count = 0, card_threshold = 20, card_threshold_max = 3000;
- DB *db = bf->priv->file_db;
- DBC *dbc;
- DBT id_dbt, vcard_dbt;
- int file_version_name_len;
PASBackendFileBookView *view = (PASBackendFileBookView *)cnstview;
gboolean search_needed;
@@ -262,78 +344,93 @@ pas_backend_file_search (PASBackendFile *bf,
else
pas_book_view_notify_status_message (view->book_view, _("Loading..."));
- if (view->card_sexp)
+ if (view->card_sexp) {
gtk_object_unref (GTK_OBJECT(view->card_sexp));
+ view->card_sexp = NULL;
+ }
- view->card_sexp = pas_backend_card_sexp_new (view->search);
-
- if (!view->card_sexp) {
- /* need a different error message here. */
- pas_book_view_notify_status_message (view->book_view, _("Error in search expression."));
- pas_book_view_notify_complete (view->book_view);
- return;
+ if (pas_backend_summary_is_summary_query (bf->priv->summary, view->search)) {
+ do_summary_query (bf, view);
}
+ else {
+ gint card_count = 0, card_threshold = 20, card_threshold_max = 3000;
+ int db_error = 0;
+ GList *cards = NULL;
+ DB *db = bf->priv->file_db;
+ DBC *dbc;
+ DBT id_dbt, vcard_dbt;
+ int file_version_name_len;
+
+ view->card_sexp = pas_backend_card_sexp_new (view->search);
+
+ if (!view->card_sexp) {
+ /* need a different error message here. */
+ pas_book_view_notify_status_message (view->book_view, _("Error in search expression."));
+ pas_book_view_notify_complete (view->book_view);
+ return;
+ }
- file_version_name_len = strlen (PAS_BACKEND_FILE_VERSION_NAME);
+ file_version_name_len = strlen (PAS_BACKEND_FILE_VERSION_NAME);
- db_error = db->cursor (db, NULL, &dbc, 0);
+ db_error = db->cursor (db, NULL, &dbc, 0);
- memset (&id_dbt, 0, sizeof (id_dbt));
- memset (&vcard_dbt, 0, sizeof (vcard_dbt));
+ memset (&id_dbt, 0, sizeof (id_dbt));
+ memset (&vcard_dbt, 0, sizeof (vcard_dbt));
- if (db_error != 0) {
- g_warning ("pas_backend_file_search: error building list\n");
- } else {
- db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
+ if (db_error != 0) {
+ g_warning ("pas_backend_file_search: error building list\n");
+ } else {
+ db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
- while (db_error == 0) {
+ while (db_error == 0) {
- /* don't include the version in the list of cards */
- if (id_dbt.size != file_version_name_len+1
- || strcmp (id_dbt.data, PAS_BACKEND_FILE_VERSION_NAME)) {
- char *vcard_string = vcard_dbt.data;
+ /* don't include the version in the list of cards */
+ if (id_dbt.size != file_version_name_len+1
+ || strcmp (id_dbt.data, PAS_BACKEND_FILE_VERSION_NAME)) {
+ char *vcard_string = vcard_dbt.data;
- /* check if the vcard matches the search sexp */
- if ((!search_needed) || vcard_matches_search (view, vcard_string)) {
- cards = g_list_prepend (cards, g_strdup (vcard_string));
- card_count ++;
- }
+ /* check if the vcard matches the search sexp */
+ if ((!search_needed) || vcard_matches_search (view, vcard_string)) {
+ cards = g_list_prepend (cards, g_strdup (vcard_string));
+ card_count ++;
+ }
- /* If we've accumulated a number of checks, pass them off to the client. */
- if (card_count >= card_threshold) {
- pas_book_view_notify_add (view->book_view, cards);
- /* Clean up the handed-off data. */
- g_list_foreach (cards, (GFunc)g_free, NULL);
- g_list_free (cards);
- cards = NULL;
- card_count = 0;
-
- /* Yeah, this scheme is overly complicated. But I like it. */
- if (card_threshold < card_threshold_max) {
- card_threshold = MIN (2*card_threshold, card_threshold_max);
+ /* If we've accumulated a number of checks, pass them off to the client. */
+ if (card_count >= card_threshold) {
+ pas_book_view_notify_add (view->book_view, cards);
+ /* Clean up the handed-off data. */
+ g_list_foreach (cards, (GFunc)g_free, NULL);
+ g_list_free (cards);
+ cards = NULL;
+ card_count = 0;
+
+ /* Yeah, this scheme is overly complicated. But I like it. */
+ if (card_threshold < card_threshold_max) {
+ card_threshold = MIN (2*card_threshold, card_threshold_max);
+ }
}
}
- }
- db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
- }
- dbc->c_close (dbc);
+ db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
+ }
+ dbc->c_close (dbc);
- if (db_error != DB_NOTFOUND) {
- g_warning ("pas_backend_file_search: error building list\n");
+ if (db_error != DB_NOTFOUND) {
+ g_warning ("pas_backend_file_search: error building list\n");
+ }
}
- }
- if (card_count)
- pas_book_view_notify_add (view->book_view, cards);
+ if (card_count)
+ pas_book_view_notify_add (view->book_view, cards);
- pas_book_view_notify_complete (view->book_view);
+ pas_book_view_notify_complete (view->book_view);
- /*
- ** It's fine to do this now since the data has been handed off.
- */
- g_list_foreach (cards, (GFunc)g_free, NULL);
- g_list_free (cards);
+ /*
+ ** It's fine to do this now since the data has been handed off.
+ */
+ g_list_foreach (cards, (GFunc)g_free, NULL);
+ g_list_free (cards);
+ }
}
static void
@@ -558,6 +655,9 @@ pas_backend_file_process_create_card (PASBackend *backend,
book,
GNOME_Evolution_Addressbook_BookListener_Success,
id);
+
+ pas_backend_summary_add_card (bf->priv->summary, vcard);
+
g_free(vcard);
g_free(id);
}
@@ -624,6 +724,7 @@ pas_backend_file_process_remove_card (PASBackend *backend,
pas_book_respond_remove (
book,
GNOME_Evolution_Addressbook_BookListener_Success);
+ pas_backend_summary_remove_card (bf->priv->summary, id);
}
static void
@@ -707,6 +808,9 @@ pas_backend_file_process_modify_card (PASBackend *backend,
pas_book_respond_modify (
book,
GNOME_Evolution_Addressbook_BookListener_Success);
+
+ pas_backend_summary_remove_card (bf->priv->summary, id);
+ pas_backend_summary_add_card (bf->priv->summary, req->vcard);
}
else {
pas_book_respond_modify (
@@ -1301,6 +1405,11 @@ pas_backend_file_load_uri (PASBackend *backend,
g_free (bf->priv->filename);
bf->priv->filename = filename;
+ bf->priv->summary = pas_backend_summary_new (filename, SUMMARY_FLUSH_TIMEOUT);
+
+ if (!pas_backend_summary_load (bf->priv->summary))
+ build_summary (bf->priv);
+
return GNOME_Evolution_Addressbook_BookListener_Success;
}
@@ -1446,6 +1555,7 @@ pas_backend_file_destroy (GtkObject *object)
bf = PAS_BACKEND_FILE (object);
gtk_object_unref(GTK_OBJECT(bf->priv->book_views));
+ gtk_object_unref(GTK_OBJECT(bf->priv->summary));
g_free (bf->priv->uri);
g_free (bf->priv->filename);
diff --git a/addressbook/backend/pas/pas-backend-summary.c b/addressbook/backend/pas/pas-backend-summary.c
new file mode 100644
index 0000000000..3972426367
--- /dev/null
+++ b/addressbook/backend/pas/pas-backend-summary.c
@@ -0,0 +1,957 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pas-backend-summary.c
+ * Copyright 2000, 2001, Ximian, Inc.
+ *
+ * Authors:
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <netinet/in.h>
+
+#include <gal/widgets/e-unicode.h>
+
+#include "ebook/e-card-simple.h"
+#include "pas-backend-summary.h"
+#include "e-util/e-sexp.h"
+
+static GtkObjectClass *parent_class;
+
+struct _PASBackendSummaryPrivate {
+ char *db_path;
+ char *summary_path;
+ guint32 file_version;
+ gboolean dirty;
+ int flush_timeout_millis;
+ int flush_timeout;
+ GPtrArray *items;
+#ifdef SUMMARY_STATS
+ int size;
+#endif
+};
+
+typedef struct {
+ char *id;
+ char *nickname;
+ char *given_name;
+ char *surname;
+ char *file_as;
+ char *email_1;
+ char *email_2;
+ char *email_3;
+} PASBackendSummaryItem;
+
+typedef struct {
+ /* these lengths do *not* including the terminating \0, as
+ it's not stored on disk. */
+ guint16 id_len;
+ guint16 nickname_len;
+ guint16 given_name_len;
+ guint16 surname_len;
+ guint16 file_as_len;
+ guint16 email_1_len;
+ guint16 email_2_len;
+ guint16 email_3_len;
+} PASBackendSummaryDiskItem_1_0;
+
+typedef struct {
+ guint32 file_version;
+ guint32 num_items;
+} PASBackendSummaryHeader;
+
+#define PAS_SUMMARY_MAGIC "PAS-SUMMARY"
+#define PAS_SUMMARY_MAGIC_LEN 11
+
+#define PAS_SUMMARY_FILE_VERSION_1_0 1000
+
+#define PAS_SUMMARY_FILE_VERSION PAS_SUMMARY_FILE_VERSION_1_0
+
+static void
+free_summary_item (PASBackendSummaryItem *item)
+{
+ g_free (item->id);
+ g_free (item->nickname);
+ g_free (item->surname);
+ g_free (item->file_as);
+ g_free (item->email_1);
+ g_free (item->email_2);
+ g_free (item->email_3);
+ g_free (item);
+}
+
+static void
+clear_items (PASBackendSummary *summary)
+{
+ int i;
+ for (i = 0; i < summary->priv->items->len; i++) {
+ PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
+ free_summary_item (item);
+ }
+}
+
+PASBackendSummary*
+pas_backend_summary_new (const char *db_path, int flush_timeout_millis)
+{
+ PASBackendSummary *summary = gtk_type_new (PAS_BACKEND_SUMMARY_TYPE);
+
+ summary->priv->db_path = g_strdup (db_path);
+ summary->priv->summary_path = g_strconcat (db_path, ".summary", NULL);
+ summary->priv->flush_timeout_millis = flush_timeout_millis;
+ summary->priv->file_version = PAS_SUMMARY_FILE_VERSION_1_0;
+
+ return summary;
+}
+
+static void
+pas_backend_summary_destroy (GtkObject *object)
+{
+ PASBackendSummary *summary = PAS_BACKEND_SUMMARY (object);
+
+ if (summary->priv->dirty)
+ g_warning ("Destroying dirty summary");
+
+ if (summary->priv->flush_timeout) {
+ gtk_timeout_remove (summary->priv->flush_timeout);
+ summary->priv->flush_timeout = 0;
+ }
+
+ g_free (summary->priv->db_path);
+ g_free (summary->priv->summary_path);
+ clear_items (summary);
+ g_ptr_array_free (summary->priv->items, TRUE);
+
+ g_free (summary->priv);
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+pas_backend_summary_class_init (PASBackendSummaryClass *klass)
+{
+ GtkObjectClass *object_class = (GtkObjectClass *) klass;
+
+ parent_class = gtk_type_class (gtk_object_get_type ());
+
+ /* Set the virtual methods. */
+
+ object_class->destroy = pas_backend_summary_destroy;
+}
+
+static void
+pas_backend_summary_init (PASBackendSummary *summary)
+{
+ PASBackendSummaryPrivate *priv;
+
+ priv = g_new(PASBackendSummaryPrivate, 1);
+
+ summary->priv = priv;
+
+ priv->db_path = NULL;
+ priv->summary_path = NULL;
+ priv->dirty = FALSE;
+ priv->items = g_ptr_array_new();
+ priv->flush_timeout_millis = 0;
+ priv->flush_timeout = 0;
+#ifdef SUMMARY_STATS
+ priv->size = 0;
+#endif
+}
+
+/**
+ * pas_backend_summary_get_type:
+ */
+GtkType
+pas_backend_summary_get_type (void)
+{
+ static GtkType type = 0;
+
+ if (! type) {
+ GtkTypeInfo info = {
+ "PASBackendSummary",
+ sizeof (PASBackendSummary),
+ sizeof (PASBackendSummaryClass),
+ (GtkClassInitFunc) pas_backend_summary_class_init,
+ (GtkObjectInitFunc) pas_backend_summary_init,
+ NULL, /* reserved 1 */
+ NULL, /* reserved 2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ type = gtk_type_unique (gtk_object_get_type (), &info);
+ }
+
+ return type;
+}
+
+
+static gboolean
+pas_backend_summary_check_magic (PASBackendSummary *summary, FILE *fp)
+{
+ char buf [PAS_SUMMARY_MAGIC_LEN + 1];
+ int rv;
+
+ memset (buf, 0, sizeof (buf));
+
+ rv = fread (buf, PAS_SUMMARY_MAGIC_LEN, 1, fp);
+ if (rv != 1)
+ return FALSE;
+ if (strcmp (buf, PAS_SUMMARY_MAGIC))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+pas_backend_summary_load_header (PASBackendSummary *summary, FILE *fp,
+ PASBackendSummaryHeader *header)
+{
+ int rv;
+
+ rv = fread (&header->file_version, sizeof (header->file_version), 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ header->file_version = ntohl (header->file_version);
+ if (header->file_version != PAS_SUMMARY_FILE_VERSION) {
+ /* XXX upgrade stuff in here, but since there's only 1
+ version now return FALSE */
+ return FALSE;
+ }
+
+ rv = fread (&header->num_items, sizeof (header->num_items), 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ header->num_items = ntohl (header->num_items);
+
+ return TRUE;
+}
+
+static char *
+read_string (FILE *fp, int len)
+{
+ char *buf;
+ int rv;
+
+ buf = g_new0 (char, len + 1);
+
+ rv = fread (buf, len, 1, fp);
+ if (rv != 1) {
+ g_free (buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+static gboolean
+pas_backend_summary_load_item (PASBackendSummary *summary, FILE *fp,
+ PASBackendSummaryItem **new_item)
+{
+ PASBackendSummaryItem *item;
+ char *buf;
+
+ if (summary->priv->file_version == PAS_SUMMARY_FILE_VERSION_1_0) {
+ PASBackendSummaryDiskItem_1_0 disk_item;
+ int rv = fread (&disk_item, sizeof (disk_item), 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ disk_item.id_len = ntohs (disk_item.id_len);
+ disk_item.nickname_len = ntohs (disk_item.nickname_len);
+ disk_item.given_name_len = ntohs (disk_item.given_name_len);
+ disk_item.surname_len = ntohs (disk_item.surname_len);
+ disk_item.file_as_len = ntohs (disk_item.file_as_len);
+ disk_item.email_1_len = ntohs (disk_item.email_1_len);
+ disk_item.email_2_len = ntohs (disk_item.email_2_len);
+ disk_item.email_3_len = ntohs (disk_item.email_3_len);
+
+ item = g_new0 (PASBackendSummaryItem, 1);
+
+ if (disk_item.id_len) {
+ buf = read_string (fp, disk_item.id_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->id = buf;
+ }
+
+ if (disk_item.nickname_len) {
+ buf = read_string (fp, disk_item.nickname_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->nickname = buf;
+ }
+
+ if (disk_item.given_name_len) {
+ buf = read_string (fp, disk_item.given_name_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->given_name = buf;
+ }
+
+ if (disk_item.surname_len) {
+ buf = read_string (fp, disk_item.surname_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->surname = buf;
+ }
+
+ if (disk_item.file_as_len) {
+ buf = read_string (fp, disk_item.file_as_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->file_as = buf;
+ }
+
+ if (disk_item.email_1_len) {
+ buf = read_string (fp, disk_item.email_1_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->email_1 = buf;
+ }
+
+ if (disk_item.email_2_len) {
+ buf = read_string (fp, disk_item.email_2_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->email_2 = buf;
+ }
+
+ if (disk_item.email_3_len) {
+ buf = read_string (fp, disk_item.email_3_len);
+ if (!buf) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ item->email_3 = buf;
+ }
+
+ /* the only field that has to be there is the id */
+ if (!item->id) {
+ free_summary_item (item);
+ return FALSE;
+ }
+ }
+ else {
+ /* unhandled file version */
+ return FALSE;
+ }
+
+ *new_item = item;
+ return TRUE;
+}
+
+gboolean
+pas_backend_summary_load (PASBackendSummary *summary)
+{
+ struct stat sb;
+ time_t db_mtime, summary_mtime;
+
+ /* we don't have a way to determine what was added since we
+ last updated the summary (without traversing the entire db
+ anyway), so if the db is newer we just lose the on-disk
+ summary */
+
+ if (stat (summary->priv->db_path, &sb) == -1) {
+ g_warning ("no db present for summary load");
+ return FALSE;
+ }
+ db_mtime = sb.st_mtime;
+
+ if (stat (summary->priv->summary_path, &sb) == -1) {
+ /* if there's no summary present, look for the .new
+ file and rename it if it's there, and attempt to
+ load that */
+ char *new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
+ if (stat (new_filename, &sb) == -1) {
+ g_warning ("no summary present");
+ g_free (new_filename);
+ return FALSE;
+ }
+ else {
+ rename (new_filename, summary->priv->summary_path);
+ stat (summary->priv->summary_path, &sb);
+ g_free (new_filename);
+ }
+
+ }
+ summary_mtime = sb.st_mtime;
+
+ if (summary_mtime < db_mtime) {
+ /* we need to regenerate the summary */
+ return FALSE;
+ }
+ else {
+ /* the mtime is ok, load the summary */
+ PASBackendSummaryHeader header;
+ PASBackendSummaryItem *new_item;
+ FILE *fp = fopen (summary->priv->summary_path, "r");
+ int i;
+
+ if (!fp) {
+ g_warning ("failed to open summary file");
+ return FALSE;
+ }
+
+ if (!pas_backend_summary_check_magic (summary, fp)) {
+ g_warning ("file is not a valid summary file");
+ fclose (fp);
+ return FALSE;
+ }
+
+ if (!pas_backend_summary_load_header (summary, fp, &header)) {
+ g_warning ("failed to read summary header");
+ fclose (fp);
+ return FALSE;
+ }
+
+ summary->priv->file_version = header.file_version;
+
+ for (i = 0; i < header.num_items; i ++) {
+ if (!pas_backend_summary_load_item (summary, fp, &new_item)) {
+ g_warning ("error while reading summary item");
+ clear_items (summary);
+ fclose (fp);
+ return FALSE;
+ }
+
+ g_ptr_array_add (summary->priv->items, new_item);
+ }
+
+ /* XXX for now return FALSE so we'll regenerate the summary */
+ return TRUE;
+ }
+}
+
+static gboolean
+pas_backend_summary_save_magic (FILE *fp)
+{
+ int rv;
+ rv = fwrite (PAS_SUMMARY_MAGIC, PAS_SUMMARY_MAGIC_LEN, 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+pas_backend_summary_save_header (PASBackendSummary *summary, FILE *fp)
+{
+ PASBackendSummaryHeader header;
+ int rv;
+
+ header.file_version = htonl (summary->priv->file_version);
+ header.num_items = htonl (summary->priv->items->len);
+
+ rv = fwrite (&header, sizeof (header), 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+save_string (const char *str, FILE *fp)
+{
+ int rv;
+
+ if (!str)
+ return TRUE;
+
+ rv = fwrite (str, strlen (str), 1, fp);
+ return (rv == 1);
+}
+
+static gboolean
+pas_backend_summary_save_item (PASBackendSummary *summary, FILE *fp, PASBackendSummaryItem *item)
+{
+ PASBackendSummaryDiskItem_1_0 disk_item;
+ int len;
+ int rv;
+
+ len = item->id ? strlen (item->id) : 0;
+ disk_item.id_len = htons (len);
+
+ len = item->nickname ? strlen (item->nickname) : 0;
+ disk_item.nickname_len = htons (len);
+
+ len = item->given_name ? strlen (item->given_name) : 0;
+ disk_item.given_name_len = htons (len);
+
+ len = item->surname ? strlen (item->surname) : 0;
+ disk_item.surname_len = htons (len);
+
+ len = item->file_as ? strlen (item->file_as) : 0;
+ disk_item.file_as_len = htons (len);
+
+ len = item->email_1 ? strlen (item->email_1) : 0;
+ disk_item.email_1_len = htons (len);
+
+ len = item->email_2 ? strlen (item->email_2) : 0;
+ disk_item.email_2_len = htons (len);
+
+ len = item->email_3 ? strlen (item->email_3) : 0;
+ disk_item.email_3_len = htons (len);
+
+ rv = fwrite (&disk_item, sizeof(disk_item), 1, fp);
+ if (rv != 1)
+ return FALSE;
+
+ if (!save_string (item->id, fp))
+ return FALSE;
+ if (!save_string (item->nickname, fp))
+ return FALSE;
+ if (!save_string (item->given_name, fp))
+ return FALSE;
+ if (!save_string (item->surname, fp))
+ return FALSE;
+ if (!save_string (item->file_as, fp))
+ return FALSE;
+ if (!save_string (item->email_1, fp))
+ return FALSE;
+ if (!save_string (item->email_2, fp))
+ return FALSE;
+ if (!save_string (item->email_3, fp))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+pas_backend_summary_save (PASBackendSummary *summary)
+{
+ FILE *fp = NULL;
+ char *new_filename = NULL;
+ int i;
+
+ if (!summary->priv->dirty)
+ return TRUE;
+
+ new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
+
+ fp = fopen (new_filename, "w");
+ if (!fp) {
+ g_warning ("could not create new summary file");
+ goto lose;
+ }
+
+ if (!pas_backend_summary_save_magic (fp)) {
+ g_warning ("could not write magic to new summary file");
+ goto lose;
+ }
+
+ if (!pas_backend_summary_save_header (summary, fp)) {
+ g_warning ("could not write header to new summary file");
+ goto lose;
+ }
+
+ for (i = 0; i < summary->priv->items->len; i ++) {
+ PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
+ if (!pas_backend_summary_save_item (summary, fp, item)) {
+ g_warning ("failed to write an item to new summary file");
+ goto lose;
+ }
+ }
+
+ fclose (fp);
+
+ /* if we have a queued flush, clear it (since we just flushed) */
+ if (summary->priv->flush_timeout) {
+ gtk_timeout_remove (summary->priv->flush_timeout);
+ summary->priv->flush_timeout = 0;
+ }
+
+ /* unlink the old summary and rename the new one */
+ unlink (summary->priv->summary_path);
+ rename (new_filename, summary->priv->summary_path);
+
+ g_free (new_filename);
+
+ return TRUE;
+
+ lose:
+ if (fp)
+ fclose (fp);
+ if (new_filename)
+ unlink (new_filename);
+ g_free (new_filename);
+ return FALSE;
+}
+
+void
+pas_backend_summary_add_card (PASBackendSummary *summary, const char *vcard)
+{
+ ECard *card;
+ ECardSimple *simple;
+ PASBackendSummaryItem *new_item;
+
+ card = e_card_new ((char*)vcard);
+ simple = e_card_simple_new (card);
+
+ new_item = g_new (PASBackendSummaryItem, 1);
+
+ new_item->id = g_strdup (e_card_simple_get_id (simple));
+ new_item->nickname = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_NICKNAME);
+ new_item->given_name = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_GIVEN_NAME);
+ new_item->surname = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_FAMILY_NAME);
+ new_item->file_as = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_FILE_AS);
+ new_item->email_1 = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL);
+ new_item->email_2 = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL_2);
+ new_item->email_3 = e_card_simple_get (simple, E_CARD_SIMPLE_FIELD_EMAIL_3);
+
+ g_ptr_array_add (summary->priv->items, new_item);
+
+ gtk_object_unref (GTK_OBJECT (simple));
+ gtk_object_unref (GTK_OBJECT (card));
+
+#ifdef SUMMARY_STATS
+ summary->priv->size += sizeof (PASBackendSummaryItem);
+ summary->priv->size += new_item->id ? strlen (new_item->id) : 0;
+ summary->priv->size += new_item->nickname ? strlen (new_item->nickname) : 0;
+ summary->priv->size += new_item->given_name ? strlen (new_item->given_name) : 0;
+ summary->priv->size += new_item->surname ? strlen (new_item->surname) : 0;
+ summary->priv->size += new_item->file_as ? strlen (new_item->file_as) : 0;
+ summary->priv->size += new_item->email_1 ? strlen (new_item->email_1) : 0;
+ summary->priv->size += new_item->email_2 ? strlen (new_item->email_2) : 0;
+ summary->priv->size += new_item->email_3 ? strlen (new_item->email_3) : 0;
+#endif
+ pas_backend_summary_touch (summary);
+}
+
+void
+pas_backend_summary_remove_card (PASBackendSummary *summary, const char *id)
+{
+ int i;
+
+ for (i = 0; i < summary->priv->items->len; i ++) {
+ PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
+ if (!strcmp (item->id, id)) {
+ g_ptr_array_remove_index (summary->priv->items, i);
+ free_summary_item (item);
+ pas_backend_summary_touch (summary);
+ return;
+ }
+ }
+
+ g_warning ("pas_backend_summary_remove_card: unable to locate id `%s'", id);
+}
+
+static int
+summary_flush_func (gpointer data)
+{
+ PASBackendSummary *summary = PAS_BACKEND_SUMMARY (data);
+
+ if (!pas_backend_summary_save (summary)) {
+ /* this isn't fatal, as we can just either 1) flush
+ out with the next change, or 2) regen the summary
+ when we next load the uri */
+ g_warning ("failed to flush summary file to disk");
+ return TRUE; /* try again after the next timeout */
+ }
+
+ g_warning ("flushed summary to disk");
+
+ /* we only want this to execute once, so return FALSE and set
+ summary->flush_timeout to 0 */
+ summary->priv->flush_timeout = 0;
+ return FALSE;
+}
+
+void
+pas_backend_summary_touch (PASBackendSummary *summary)
+{
+ summary->priv->dirty = TRUE;
+ if (!summary->priv->flush_timeout
+ && summary->priv->flush_timeout_millis)
+ summary->priv->flush_timeout = gtk_timeout_add (summary->priv->flush_timeout_millis,
+ summary_flush_func, summary);
+}
+
+
+/* we only want to do summary queries if the query is over the set fields in the summary */
+
+static ESExpResult *
+func_check(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+ ESExpResult *r;
+ int truth = FALSE;
+
+ if (argc == 2
+ && argv[0]->type == ESEXP_RES_STRING
+ && argv[1]->type == ESEXP_RES_STRING) {
+ char *query_name = argv[0]->value.string;
+
+ if (!strcmp (query_name, "nickname") ||
+ !strcmp (query_name, "full_name") ||
+ !strcmp (query_name, "file_as") ||
+ !strcmp (query_name, "email")) {
+ truth = TRUE;
+ }
+ }
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+
+ return r;
+}
+
+/* '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 */
+} check_symbols[] = {
+ { "contains", func_check, 0 },
+ { "is", func_check, 0 },
+ { "beginswith", func_check, 0 },
+ { "endswith", func_check, 0 },
+};
+
+gboolean
+pas_backend_summary_is_summary_query (PASBackendSummary *summary, const char *query)
+{
+ ESExp *sexp;
+ ESExpResult *r;
+ gboolean retval;
+ int i;
+ int esexp_error;
+
+ sexp = e_sexp_new();
+
+ for(i=0;i<sizeof(check_symbols)/sizeof(check_symbols[0]);i++) {
+ if (check_symbols[i].type == 1) {
+ e_sexp_add_ifunction(sexp, 0, check_symbols[i].name,
+ (ESExpIFunc *)check_symbols[i].func, summary);
+ } else {
+ e_sexp_add_function(sexp, 0, check_symbols[i].name,
+ check_symbols[i].func, summary);
+ }
+ }
+
+ e_sexp_input_text(sexp, query, strlen(query));
+ esexp_error = e_sexp_parse(sexp);
+
+ if (esexp_error == -1) {
+ return FALSE;
+ }
+
+ r = e_sexp_eval(sexp);
+
+ retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);
+
+ e_sexp_result_free(sexp, r);
+
+ e_sexp_unref (sexp);
+
+ return retval;
+}
+
+
+
+/* the actual query mechanics */
+static ESExpResult *
+do_compare (PASBackendSummary *summary, struct _ESExp *f, int argc,
+ struct _ESExpResult **argv,
+ char *(*compare)(const char*, const char*))
+{
+ GPtrArray *result = g_ptr_array_new ();
+ ESExpResult *r;
+ int i;
+
+ if (argc == 2
+ && argv[0]->type == ESEXP_RES_STRING
+ && argv[1]->type == ESEXP_RES_STRING) {
+
+ for (i = 0; i < summary->priv->items->len; i ++) {
+ PASBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
+ if (!strcmp (argv[0]->value.string, "full_name")) {
+ char *given = item->given_name;
+ char *surname = item->surname;
+ if ((given && compare (given, argv[1]->value.string))
+ || (surname && compare (surname, argv[1]->value.string)))
+ g_ptr_array_add (result, item->id);
+ }
+ else if (!strcmp (argv[0]->value.string, "email")) {
+ char *email_1 = item->email_1;
+ char *email_2 = item->email_2;
+ char *email_3 = item->email_3;
+ if ((email_1 && compare (email_1, argv[1]->value.string))
+ || (email_2 && compare (email_2, argv[1]->value.string))
+ || (email_3 && compare (email_3, argv[1]->value.string)))
+ g_ptr_array_add (result, item->id);
+ }
+ else if (!strcmp (argv[0]->value.string, "file_as")) {
+ char *file_as = item->file_as;
+ if (file_as && compare (file_as, argv[1]->value.string))
+ g_ptr_array_add (result, item->id);
+ }
+ else if (!strcmp (argv[0]->value.string, "nickname")) {
+ char *nickname = item->nickname;
+ if (nickname && compare (nickname, argv[1]->value.string))
+ g_ptr_array_add (result, item->id);
+ }
+ }
+ }
+
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = result;
+
+ return r;
+}
+
+static ESExpResult *
+func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+ PASBackendSummary *summary = data;
+
+ return do_compare (summary, f, argc, argv, (char *(*)(const char*, const char*)) e_utf8_strstrcase);
+}
+
+static char *
+is_helper (const char *s1, const char *s2)
+{
+ if (!strcmp(s1, s2))
+ return (char*)s1;
+ else
+ return NULL;
+}
+
+static ESExpResult *
+func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+ PASBackendSummary *summary = data;
+
+ return do_compare (summary, f, argc, argv, is_helper);
+}
+
+static char *
+endswith_helper (const char *s1, const char *s2)
+{
+ char *p;
+ if ((p = (char*)e_utf8_strstrcase(s1, s2))
+ && (strlen(p) == strlen(s2)))
+ return p;
+ else
+ return NULL;
+}
+
+static ESExpResult *
+func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+ PASBackendSummary *summary = data;
+
+ return do_compare (summary, f, argc, argv, endswith_helper);
+}
+
+static char *
+beginswith_helper (const char *s1, const char *s2)
+{
+ char *p;
+ if ((p = (char*)e_utf8_strstrcase(s1, s2))
+ && (p == s1))
+ return p;
+ else
+ return NULL;
+}
+
+static ESExpResult *
+func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+ PASBackendSummary *summary = data;
+
+ return do_compare (summary, f, argc, argv, beginswith_helper);
+}
+
+/* '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[] = {
+ { "contains", func_contains, 0 },
+ { "is", func_is, 0 },
+ { "beginswith", func_beginswith, 0 },
+ { "endswith", func_endswith, 0 },
+};
+
+GPtrArray*
+pas_backend_summary_search (PASBackendSummary *summary, const char *query)
+{
+ ESExp *sexp;
+ ESExpResult *r;
+ GPtrArray *retval = g_ptr_array_new();
+ int i;
+ int esexp_error;
+
+ sexp = e_sexp_new();
+
+ for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
+ if (symbols[i].type == 1) {
+ e_sexp_add_ifunction(sexp, 0, symbols[i].name,
+ (ESExpIFunc *)symbols[i].func, summary);
+ } else {
+ e_sexp_add_function(sexp, 0, symbols[i].name,
+ symbols[i].func, summary);
+ }
+ }
+
+ e_sexp_input_text(sexp, query, strlen(query));
+ esexp_error = e_sexp_parse(sexp);
+
+ if (esexp_error == -1) {
+ return NULL;
+ }
+
+ r = e_sexp_eval(sexp);
+
+ if (r && r->type == ESEXP_RES_ARRAY_PTR && r->value.ptrarray) {
+ GPtrArray *ptrarray = r->value.ptrarray;
+ int i;
+
+ for (i = 0; i < ptrarray->len; i ++)
+ g_ptr_array_add (retval, g_ptr_array_index (ptrarray, i));
+ }
+
+ e_sexp_result_free(sexp, r);
+
+ e_sexp_unref (sexp);
+
+ return retval;
+}
diff --git a/addressbook/backend/pas/pas-backend-summary.h b/addressbook/backend/pas/pas-backend-summary.h
new file mode 100644
index 0000000000..8feac77339
--- /dev/null
+++ b/addressbook/backend/pas/pas-backend-summary.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * pas-backend-summary.h
+ * Copyright 2000, 2001, Ximian, Inc.
+ *
+ * Authors:
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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 __PAS_BACKEND_SUMMARY_H__
+#define __PAS_BACKEND_SUMMARY_H__
+
+#include <gtk/gtk.h>
+
+typedef struct _PASBackendSummaryPrivate PASBackendSummaryPrivate;
+
+typedef struct {
+ GtkObject parent_object;
+ PASBackendSummaryPrivate *priv;
+} PASBackendSummary;
+
+typedef struct {
+ GtkObjectClass parent_class;
+} PASBackendSummaryClass;
+
+PASBackendSummary* pas_backend_summary_new (const char *db_path, int flush_timeout_millis);
+GtkType pas_backend_summary_get_type (void);
+
+/* returns FALSE if the load fails for any reason (including that the
+ summary is out of date), TRUE if it succeeds */
+gboolean pas_backend_summary_load (PASBackendSummary *summary);
+/* returns FALSE if the save fails, TRUE if it succeeds (or isn't required due to no changes) */
+gboolean pas_backend_summary_save (PASBackendSummary *summary);
+
+void pas_backend_summary_add_card (PASBackendSummary *summary, const char *vcard);
+void pas_backend_summary_remove_card (PASBackendSummary *summary, const char *id);
+
+void pas_backend_summary_touch (PASBackendSummary *summary);
+
+gboolean pas_backend_summary_is_summary_query (PASBackendSummary *summary, const char *query);
+GPtrArray* pas_backend_summary_search (PASBackendSummary *summary, const char *query);
+
+#define PAS_BACKEND_SUMMARY_TYPE (pas_backend_summary_get_type ())
+#define PAS_BACKEND_SUMMARY(o) (GTK_CHECK_CAST ((o), PAS_BACKEND_SUMMARY_TYPE, PASBackendSummary))
+#define PAS_BACKEND_SUMMARY_CLASS(k) (GTK_CHECK_CLASS_CAST((k), PAS_BACKEND_TYPE, PASBackendSummaryClass))
+#define PAS_IS_BACKEND_SUMMARY(o) (GTK_CHECK_TYPE ((o), PAS_BACKEND_SUMMARY_TYPE))
+#define PAS_IS_BACKEND_SUMMARY_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), PAS_BACKEND_SUMMARY_TYPE))
+
+#endif /* __PAS_BACKEND_SUMMARY_H__ */