aboutsummaryrefslogtreecommitdiffstats
path: root/addressbook/backend/pas/pas-backend-vcf.c
diff options
context:
space:
mode:
Diffstat (limited to 'addressbook/backend/pas/pas-backend-vcf.c')
-rw-r--r--addressbook/backend/pas/pas-backend-vcf.c632
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;
+}