diff options
-rw-r--r-- | addressbook/ChangeLog | 26 | ||||
-rw-r--r-- | addressbook/backend/pas/Makefile.am | 2 | ||||
-rw-r--r-- | addressbook/backend/pas/pas-backend-file.c | 246 | ||||
-rw-r--r-- | addressbook/backend/pas/pas-backend-summary.c | 957 | ||||
-rw-r--r-- | addressbook/backend/pas/pas-backend-summary.h | 63 |
5 files changed, 1226 insertions, 68 deletions
diff --git a/addressbook/ChangeLog b/addressbook/ChangeLog index 6a36e43b96..ebbc18bcd6 100644 --- a/addressbook/ChangeLog +++ b/addressbook/ChangeLog @@ -1,3 +1,29 @@ +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. + 2002-06-25 Chris Toshok <toshok@ximian.com> * gui/component/addressbook-component.c (create_component): 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__ */ |