diff options
Diffstat (limited to 'addressbook/backend/pas/pas-backend-vcf.c')
-rw-r--r-- | addressbook/backend/pas/pas-backend-vcf.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/addressbook/backend/pas/pas-backend-vcf.c b/addressbook/backend/pas/pas-backend-vcf.c new file mode 100644 index 0000000000..3765fd978e --- /dev/null +++ b/addressbook/backend/pas/pas-backend-vcf.c @@ -0,0 +1,632 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Author: + * Chris Toshok (toshok@ximian.com) + * + * Copyright (C) 2003, Ximian, Inc. + */ + +#include "config.h" +#include "pas-backend-vcf.h" +#include "pas-backend-card-sexp.h" +#include "pas-book.h" +#include "pas-book-view.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <sys/stat.h> + +#include <gal/util/e-util.h> +#include <gal/widgets/e-unicode.h> + +#include <ebook/e-contact.h> +#include <libgnome/gnome-i18n.h> + +#define PAS_ID_PREFIX "pas-id-" +#define FILE_FLUSH_TIMEOUT 5000 + +static PASBackendSyncClass *pas_backend_vcf_parent_class; +typedef struct _PASBackendVCFBookView PASBackendVCFBookView; +typedef struct _PASBackendVCFSearchContext PASBackendVCFSearchContext; + +struct _PASBackendVCFPrivate { + char *uri; + char *filename; + GHashTable *contacts; + gboolean dirty; + int flush_timeout_tag; +}; + +static char * +pas_backend_vcf_create_unique_id () +{ + /* use a 32 counter and the 32 bit timestamp to make an id. + it's doubtful 2^32 id's will be created in a second, so we + should be okay. */ + static guint c = 0; + return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", time(NULL), c++); +} + +typedef struct { + PASBackendVCF *bvcf; + PASBook *book; + PASBookView *view; +} VCFBackendSearchClosure; + +static void +free_search_closure (VCFBackendSearchClosure *closure) +{ + g_free (closure); +} + +static void +foreach_search_compare (char *id, char *vcard_string, VCFBackendSearchClosure *closure) +{ + EContact *contact; + + contact = e_contact_new_from_vcard (vcard_string); + pas_book_view_notify_update (closure->view, contact); + g_object_unref (contact); +} + +static gboolean +pas_backend_vcf_search_timeout (gpointer data) +{ + VCFBackendSearchClosure *closure = data; + + g_hash_table_foreach (closure->bvcf->priv->contacts, + (GHFunc)foreach_search_compare, + closure); + + pas_book_view_notify_complete (closure->view, GNOME_Evolution_Addressbook_Success); + + free_search_closure (closure); + + return FALSE; +} + + +static void +pas_backend_vcf_search (PASBackendVCF *bvcf, + PASBookView *book_view) +{ + const char *query = pas_book_view_get_card_query (book_view); + VCFBackendSearchClosure *closure = g_new0 (VCFBackendSearchClosure, 1); + + if ( ! strcmp (query, "(contains \"x-evolution-any-field\" \"\")")) + pas_book_view_notify_status_message (book_view, _("Loading...")); + else + pas_book_view_notify_status_message (book_view, _("Searching...")); + + closure->view = book_view; + closure->bvcf = bvcf; + + g_idle_add (pas_backend_vcf_search_timeout, closure); +} + +static void +insert_contact (PASBackendVCF *vcf, char *vcard) +{ + EContact *contact = e_contact_new_from_vcard (vcard); + char *id; + + id = e_contact_get (contact, E_CONTACT_UID); + if (id) + g_hash_table_insert (vcf->priv->contacts, + id, + e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30)); +} + +static void +load_file (PASBackendVCF *vcf) +{ + FILE *fp; + GString *str; + char buf[1024]; + + fp = fopen (vcf->priv->filename, "r"); + if (!fp) { + g_warning ("failed to open `%s' for reading", vcf->priv->filename); + return; + } + + str = g_string_new (""); + + while (fgets (buf, sizeof (buf), fp)) { + if (!strcmp (buf, "\r\n")) { + /* if the string has accumulated some stuff, create a contact for it and start over */ + if (str->len) { + insert_contact (vcf, str->str); + g_string_assign (str, ""); + } + } + else { + g_string_append (str, buf); + } + } + if (str->len) { + insert_contact (vcf, str->str); + } + + g_string_free (str, TRUE); + + fclose (fp); +} + +static void +foreach_build_list (char *id, char *vcard_string, GList **list) +{ + *list = g_list_append (*list, e_contact_new_from_vcard (vcard_string)); +} + +static gboolean +save_file (PASBackendVCF *vcf) +{ + GList *contacts = NULL; + GList *l; + char *new_path; + int fd, rv; + + g_hash_table_foreach (vcf->priv->contacts, (GHFunc)foreach_build_list, &contacts); + + new_path = g_strdup_printf ("%s.new", vcf->priv->filename); + + fd = open (new_path, O_CREAT | O_TRUNC | O_WRONLY, 0666); + + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + char *vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30); + int len = strlen (vcard_str); + + rv = write (fd, vcard_str, len); + g_free (vcard_str); + if (rv < len) { + /* XXX */ + g_warning ("write failed. we need to handle short writes\n"); + close (fd); + unlink (new_path); + return FALSE; + } + + rv = write (fd, "\r\n", 2); + if (rv < 2) { + /* XXX */ + g_warning ("write failed. we need to handle short writes\n"); + close (fd); + unlink (new_path); + return FALSE; + } + } + + if (0 > rename (new_path, vcf->priv->filename)) { + g_warning ("Failed to rename %s: %s\n", vcf->priv->filename, strerror(errno)); + unlink (new_path); + return FALSE; + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); + g_free (new_path); + + vcf->priv->dirty = FALSE; + + return TRUE; +} + +static gboolean +vcf_flush_file (gpointer data) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (data); + + if (!bvcf->priv->dirty) { + bvcf->priv->flush_timeout_tag = 0; + return FALSE; + } + + if (!save_file (bvcf)) { + g_warning ("failed to flush the .vcf file to disk, will try again next timeout"); + return TRUE; + } + + bvcf->priv->flush_timeout_tag = 0; + return FALSE; +} + +static EContact * +do_create(PASBackendVCF *bvcf, + const char *vcard_req, + gboolean dirty_the_file) +{ + char *id; + EContact *contact; + char *vcard; + + id = pas_backend_vcf_create_unique_id (); + + contact = e_contact_new_from_vcard (vcard_req); + e_contact_set(contact, E_CONTACT_UID, id); + vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30); + + g_hash_table_insert (bvcf->priv->contacts, id, vcard); + + if (dirty_the_file) { + bvcf->priv->dirty = TRUE; + + if (!bvcf->priv->flush_timeout_tag) + bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT, + vcf_flush_file, bvcf); + } + + return contact; +} + +static PASBackendSyncStatus +pas_backend_vcf_process_create_contact (PASBackendSync *backend, + PASBook *book, + const char *vcard, + EContact **contact) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + + *contact = do_create(bvcf, vcard, TRUE); + if (*contact) { + return GNOME_Evolution_Addressbook_Success; + } + else { + /* XXX need a different call status for this case, i + think */ + return GNOME_Evolution_Addressbook_ContactNotFound; + } +} + +static PASBackendSyncStatus +pas_backend_vcf_process_remove_contacts (PASBackendSync *backend, + PASBook *book, + GList *id_list, + GList **ids) +{ + /* FIXME: make this handle bulk deletes like the file backend does */ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + char *id = id_list->data; + + if (!g_hash_table_remove (bvcf->priv->contacts, id)) { + return GNOME_Evolution_Addressbook_ContactNotFound; + } + else { + bvcf->priv->dirty = TRUE; + if (!bvcf->priv->flush_timeout_tag) + bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT, + vcf_flush_file, bvcf); + + *ids = g_list_append (*ids, id); + + return GNOME_Evolution_Addressbook_Success; + } +} + +static PASBackendSyncStatus +pas_backend_vcf_process_modify_contact (PASBackendSync *backend, + PASBook *book, + const char *vcard, + EContact **contact) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + char *old_id, *old_vcard_string; + const char *id; + + /* create a new ecard from the request data */ + *contact = e_contact_new_from_vcard (vcard); + id = e_contact_get_const (*contact, E_CONTACT_UID); + + if (!g_hash_table_lookup_extended (bvcf->priv->contacts, id, (gpointer)&old_id, (gpointer)&old_vcard_string)) { + return GNOME_Evolution_Addressbook_ContactNotFound; + } + else { + g_hash_table_insert (bvcf->priv->contacts, old_id, g_strdup (vcard)); + + g_free (old_vcard_string); + + return GNOME_Evolution_Addressbook_Success; + } +} + +static PASBackendSyncStatus +pas_backend_vcf_process_get_contact (PASBackendSync *backend, + PASBook *book, + const char *id, + char **vcard) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + char *v; + + v = g_hash_table_lookup (bvcf->priv->contacts, id); + + if (v) { + *vcard = g_strdup (v); + return GNOME_Evolution_Addressbook_Success; + } else { + *vcard = g_strdup (""); + return GNOME_Evolution_Addressbook_ContactNotFound; + } +} + + +typedef struct { + PASBackendVCF *bvcf; + gboolean search_needed; + PASBackendCardSExp *card_sexp; + GList *list; +} GetContactListClosure; + +static void +foreach_get_contact_compare (char *id, char *vcard_string, GetContactListClosure *closure) +{ + if ((!closure->search_needed) || pas_backend_card_sexp_match_vcard (closure->card_sexp, vcard_string)) { + closure->list = g_list_append (closure->list, g_strdup (vcard_string)); + } +} + +static PASBackendSyncStatus +pas_backend_vcf_process_get_contact_list (PASBackendSync *backend, + PASBook *book, + const char *query, + GList **contacts) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + const char *search = query; + GetContactListClosure closure; + + closure.bvcf = bvcf; + closure.search_needed = strcmp (search, "(contains \"x-evolution-any-field\" \"\")"); + closure.card_sexp = pas_backend_card_sexp_new (search); + closure.list = NULL; + + g_hash_table_foreach (bvcf->priv->contacts, (GHFunc)foreach_get_contact_compare, &closure); + + g_object_unref (closure.card_sexp); + + *contacts = closure.list; + return GNOME_Evolution_Addressbook_Success; +} + +static void +pas_backend_vcf_start_book_view (PASBackend *backend, + PASBookView *book_view) +{ + pas_backend_vcf_search (PAS_BACKEND_VCF (backend), book_view); +} + +static char * +pas_backend_vcf_extract_path_from_uri (const char *uri) +{ + g_assert (strncasecmp (uri, "vcf://", 6) == 0); + + return g_strdup (uri + 6); +} + +static PASBackendSyncStatus +pas_backend_vcf_process_authenticate_user (PASBackendSync *backend, + PASBook *book, + const char *user, + const char *passwd, + const char *auth_method) +{ + return GNOME_Evolution_Addressbook_Success; +} + +static PASBackendSyncStatus +pas_backend_vcf_process_get_supported_fields (PASBackendSync *backend, + PASBook *book, + GList **fields_out) +{ + GList *fields = NULL; + int i; + + /* XXX we need a way to say "we support everything", since the + vcf backend does */ + for (i = 0; i < E_CONTACT_FIELD_LAST; i ++) + fields = g_list_append (fields, (char*)e_contact_field_name (i)); + + *fields_out = fields; + return GNOME_Evolution_Addressbook_Success; +} + +#include "ximian-vcard.h" + +static GNOME_Evolution_Addressbook_CallStatus +pas_backend_vcf_load_uri (PASBackend *backend, + const char *uri, + gboolean only_if_exists) +{ + PASBackendVCF *bvcf = PAS_BACKEND_VCF (backend); + char *filename; + gboolean writable = FALSE; + int fd; + + g_free(bvcf->priv->uri); + bvcf->priv->uri = g_strdup (uri); + + bvcf->priv->filename = filename = pas_backend_vcf_extract_path_from_uri (uri); + + fd = open (filename, O_RDWR); + + bvcf->priv->contacts = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + if (fd != -1) { + writable = TRUE; + } else { + fd = open (filename, O_RDONLY); + + if (fd == -1) { + fd = open (filename, O_CREAT, 0666); + + if (fd != -1 && !only_if_exists) { + EContact *contact; + + contact = do_create(bvcf, XIMIAN_VCARD, FALSE); + save_file (bvcf); + + /* XXX check errors here */ + g_object_unref (contact); + + writable = TRUE; + } + } + } + + if (fd == -1) { + g_warning ("Failed to open addressbook at uri `%s'", uri); + g_warning ("error == %s", strerror(errno)); + return GNOME_Evolution_Addressbook_OtherError; + } + + close (fd); /* XXX ugh */ + load_file (bvcf); + + pas_backend_set_is_loaded (backend, TRUE); + pas_backend_set_is_writable (backend, writable); + + return GNOME_Evolution_Addressbook_Success; +} + +static char * +pas_backend_vcf_get_static_capabilities (PASBackend *backend) +{ + return g_strdup("local,do-initial-query"); +} + +static GNOME_Evolution_Addressbook_CallStatus +pas_backend_vcf_cancel_operation (PASBackend *backend, PASBook *book) +{ + return GNOME_Evolution_Addressbook_CouldNotCancel; +} + +static gboolean +pas_backend_vcf_construct (PASBackendVCF *backend) +{ + g_assert (backend != NULL); + g_assert (PAS_IS_BACKEND_VCF (backend)); + + if (! pas_backend_construct (PAS_BACKEND (backend))) + return FALSE; + + return TRUE; +} + +/** + * pas_backend_vcf_new: + */ +PASBackend * +pas_backend_vcf_new (void) +{ + PASBackendVCF *backend; + + backend = g_object_new (PAS_TYPE_BACKEND_VCF, NULL); + + if (! pas_backend_vcf_construct (backend)) { + g_object_unref (backend); + + return NULL; + } + + return PAS_BACKEND (backend); +} + +static void +pas_backend_vcf_dispose (GObject *object) +{ + PASBackendVCF *bvcf; + + bvcf = PAS_BACKEND_VCF (object); + + if (bvcf->priv) { + + if (bvcf->priv->dirty) + save_file (bvcf); + + if (bvcf->priv->flush_timeout_tag) { + g_source_remove (bvcf->priv->flush_timeout_tag); + bvcf->priv->flush_timeout_tag = 0; + } + + g_free (bvcf->priv->uri); + g_free (bvcf->priv->filename); + + g_free (bvcf->priv); + bvcf->priv = NULL; + } + + G_OBJECT_CLASS (pas_backend_vcf_parent_class)->dispose (object); +} + +static void +pas_backend_vcf_class_init (PASBackendVCFClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + PASBackendSyncClass *sync_class; + PASBackendClass *backend_class; + + pas_backend_vcf_parent_class = g_type_class_peek_parent (klass); + + sync_class = PAS_BACKEND_SYNC_CLASS (klass); + backend_class = PAS_BACKEND_CLASS (klass); + + /* Set the virtual methods. */ + backend_class->load_uri = pas_backend_vcf_load_uri; + backend_class->get_static_capabilities = pas_backend_vcf_get_static_capabilities; + backend_class->start_book_view = pas_backend_vcf_start_book_view; + backend_class->cancel_operation = pas_backend_vcf_cancel_operation; + + sync_class->create_contact_sync = pas_backend_vcf_process_create_contact; + sync_class->remove_contacts_sync = pas_backend_vcf_process_remove_contacts; + sync_class->modify_contact_sync = pas_backend_vcf_process_modify_contact; + sync_class->get_contact_sync = pas_backend_vcf_process_get_contact; + sync_class->get_contact_list_sync = pas_backend_vcf_process_get_contact_list; + sync_class->authenticate_user_sync = pas_backend_vcf_process_authenticate_user; + sync_class->get_supported_fields_sync = pas_backend_vcf_process_get_supported_fields; + + object_class->dispose = pas_backend_vcf_dispose; +} + +static void +pas_backend_vcf_init (PASBackendVCF *backend) +{ + PASBackendVCFPrivate *priv; + + priv = g_new0 (PASBackendVCFPrivate, 1); + priv->uri = NULL; + + backend->priv = priv; +} + +/** + * pas_backend_vcf_get_type: + */ +GType +pas_backend_vcf_get_type (void) +{ + static GType type = 0; + + if (! type) { + GTypeInfo info = { + sizeof (PASBackendVCFClass), + NULL, /* base_class_init */ + NULL, /* base_class_finalize */ + (GClassInitFunc) pas_backend_vcf_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (PASBackendVCF), + 0, /* n_preallocs */ + (GInstanceInitFunc) pas_backend_vcf_init + }; + + type = g_type_register_static (PAS_TYPE_BACKEND_SYNC, "PASBackendVCF", &info, 0); + } + + return type; +} |