1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* e-book-backend-vcf.c - VCF contact backend.
5 * Copyright (C) 2005 Novell, Inc.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
21 * Authors: Chris Toshok <toshok@ximian.com>
40 #include <glib/gstdio.h>
41 #include <glib/gi18n-lib.h>
43 #include "libedataserver/e-data-server-util.h"
44 #include "libedataserver/e-flag.h"
46 #include "libebook/e-contact.h"
48 #include "libedata-book/e-data-book.h"
49 #include "libedata-book/e-data-book-view.h"
50 #include "libedata-book/e-book-backend-sexp.h"
52 #include "e-book-backend-vcf.h"
54 #define PAS_ID_PREFIX "pas-id-"
55 #define FILE_FLUSH_TIMEOUT 5000
59 static EBookBackendSyncClass *e_book_backend_vcf_parent_class;
60 typedef struct _EBookBackendVCFBookView EBookBackendVCFBookView;
61 typedef struct _EBookBackendVCFSearchContext EBookBackendVCFSearchContext;
63 struct _EBookBackendVCFPrivate {
69 int flush_timeout_tag;
78 e_book_backend_vcf_create_unique_id ()
80 /* use a 32 counter and the 32 bit timestamp to make an id.
81 it's doubtful 2^32 id's will be created in a second, so we
84 return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", time(NULL), c++);
88 insert_contact (EBookBackendVCF *vcf, char *vcard)
90 EContact *contact = e_contact_new_from_vcard (vcard);
93 id = e_contact_get (contact, E_CONTACT_UID);
95 char *vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
97 vcf->priv->contact_list = g_list_prepend (vcf->priv->contact_list, vcard);
99 g_hash_table_insert (vcf->priv->contacts,
101 vcf->priv->contact_list);
106 load_file (EBookBackendVCF *vcf, int fd)
112 fp = fdopen (fd, "rb");
114 close (fd); /* callers depend on fd being closed by this function */
115 g_warning ("failed to open `%s' for reading", vcf->priv->filename);
119 str = g_string_new ("");
121 while (fgets (buf, sizeof (buf), fp)) {
122 if (!strcmp (buf, "\r\n")) {
123 /* if the string has accumulated some stuff, create a contact for it and start over */
125 insert_contact (vcf, str->str);
126 g_string_assign (str, "");
130 g_string_append (str, buf);
134 insert_contact (vcf, str->str);
137 g_string_free (str, TRUE);
143 save_file (EBookBackendVCF *vcf)
145 gboolean retv = FALSE;
150 g_warning ("EBookBackendVCF flushing file to disk");
152 g_mutex_lock (vcf->priv->mutex);
154 new_path = g_strdup_printf ("%s.new", vcf->priv->filename);
156 fd = g_open (new_path, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666);
158 g_warning ("write failed. could not open output file\n");
162 for (l = vcf->priv->contact_list; l; l = l->next) {
163 char *vcard_str = l->data;
164 int len = strlen (vcard_str);
166 rv = write (fd, vcard_str, len);
170 g_warning ("write failed. we need to handle short writes\n");
175 rv = write (fd, "\r\n\r\n", 4);
178 g_warning ("write failed. we need to handle short writes\n");
184 if (0 > g_rename (new_path, vcf->priv->filename)) {
185 g_warning ("Failed to rename %s: %s\n", vcf->priv->filename, strerror(errno));
195 vcf->priv->dirty = !retv;
196 g_mutex_unlock (vcf->priv->mutex);
202 vcf_flush_file (gpointer data)
204 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (data);
206 if (!bvcf->priv->dirty) {
207 bvcf->priv->flush_timeout_tag = 0;
211 if (!save_file (bvcf)) {
212 g_warning ("failed to flush the .vcf file to disk, will try again next timeout");
216 bvcf->priv->flush_timeout_tag = 0;
221 set_revision (EContact *contact)
223 char time_string[25] = {0};
224 const struct tm *tm = NULL;
227 g_get_current_time (&tv);
228 tm = gmtime (&tv.tv_sec);
230 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", tm);
231 e_contact_set (contact, E_CONTACT_REV, time_string);
235 do_create(EBookBackendVCF *bvcf,
236 const char *vcard_req,
237 gboolean dirty_the_file)
244 /* at the very least we need the unique_id generation to be
245 protected by the lock, even if the actual vcard parsing
247 g_mutex_lock (bvcf->priv->mutex);
248 id = e_book_backend_vcf_create_unique_id ();
250 contact = e_contact_new_from_vcard (vcard_req);
251 e_contact_set(contact, E_CONTACT_UID, id);
254 rev = e_contact_get_const (contact, E_CONTACT_REV);
256 set_revision (contact);
258 vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
260 insert_contact (bvcf, vcard);
262 if (dirty_the_file) {
263 bvcf->priv->dirty = TRUE;
265 if (!bvcf->priv->flush_timeout_tag)
266 bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT,
267 vcf_flush_file, bvcf);
270 g_mutex_unlock (bvcf->priv->mutex);
275 static EBookBackendSyncStatus
276 e_book_backend_vcf_create_contact (EBookBackendSync *backend,
282 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
284 *contact = do_create(bvcf, vcard, TRUE);
286 return GNOME_Evolution_Addressbook_Success;
289 /* XXX need a different call status for this case, i
291 return GNOME_Evolution_Addressbook_ContactNotFound;
295 static EBookBackendSyncStatus
296 e_book_backend_vcf_remove_contacts (EBookBackendSync *backend,
302 /* FIXME: make this handle bulk deletes like the file backend does */
303 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
304 char *id = id_list->data;
309 g_mutex_lock (bvcf->priv->mutex);
310 success = g_hash_table_lookup_extended (bvcf->priv->contacts, id, (gpointer)&real_id, (gpointer)&elem);
312 g_mutex_unlock (bvcf->priv->mutex);
313 return GNOME_Evolution_Addressbook_ContactNotFound;
316 success = g_hash_table_remove (bvcf->priv->contacts, real_id);
318 g_mutex_unlock (bvcf->priv->mutex);
319 return GNOME_Evolution_Addressbook_ContactNotFound;
324 bvcf->priv->contact_list = g_list_remove_link (bvcf->priv->contact_list, elem);
326 bvcf->priv->dirty = TRUE;
327 if (!bvcf->priv->flush_timeout_tag)
328 bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT,
329 vcf_flush_file, bvcf);
330 g_mutex_unlock (bvcf->priv->mutex);
332 *ids = g_list_append (*ids, id);
334 return GNOME_Evolution_Addressbook_Success;
337 static EBookBackendSyncStatus
338 e_book_backend_vcf_modify_contact (EBookBackendSync *backend,
344 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
350 /* create a new ecard from the request data */
351 *contact = e_contact_new_from_vcard (vcard);
352 id = e_contact_get_const (*contact, E_CONTACT_UID);
354 g_mutex_lock (bvcf->priv->mutex);
355 success = g_hash_table_lookup_extended (bvcf->priv->contacts, id, (gpointer)&old_id, (gpointer)&elem);
358 g_mutex_unlock (bvcf->priv->mutex);
359 return GNOME_Evolution_Addressbook_ContactNotFound;
363 elem->data = g_strdup (vcard);
364 bvcf->priv->dirty = TRUE;
365 if (!bvcf->priv->flush_timeout_tag)
366 bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT,
367 vcf_flush_file, bvcf);
368 g_mutex_unlock (bvcf->priv->mutex);
370 return GNOME_Evolution_Addressbook_Success;
373 static EBookBackendSyncStatus
374 e_book_backend_vcf_get_contact (EBookBackendSync *backend,
380 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
383 elem = g_hash_table_lookup (bvcf->priv->contacts, id);
386 *vcard = g_strdup (elem->data);
387 return GNOME_Evolution_Addressbook_Success;
389 *vcard = g_strdup ("");
390 return GNOME_Evolution_Addressbook_ContactNotFound;
396 EBookBackendVCF *bvcf;
397 gboolean search_needed;
398 EBookBackendSExp *card_sexp;
400 } GetContactListClosure;
403 foreach_get_contact_compare (char *vcard_string, GetContactListClosure *closure)
405 if ((!closure->search_needed) || e_book_backend_sexp_match_vcard (closure->card_sexp, vcard_string)) {
406 closure->list = g_list_append (closure->list, g_strdup (vcard_string));
410 static EBookBackendSyncStatus
411 e_book_backend_vcf_get_contact_list (EBookBackendSync *backend,
417 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
418 const char *search = query;
419 GetContactListClosure closure;
422 closure.search_needed = strcmp (search, "(contains \"x-evolution-any-field\" \"\")");
423 closure.card_sexp = e_book_backend_sexp_new (search);
426 g_list_foreach (bvcf->priv->contact_list, (GFunc)foreach_get_contact_compare, &closure);
428 g_object_unref (closure.card_sexp);
430 *contacts = closure.list;
431 return GNOME_Evolution_Addressbook_Success;
435 EBookBackendVCF *bvcf;
439 } VCFBackendSearchClosure;
442 closure_destroy (VCFBackendSearchClosure *closure)
444 d(printf ("destroying search closure\n"));
445 e_flag_free (closure->running);
449 static VCFBackendSearchClosure*
450 init_closure (EDataBookView *book_view, EBookBackendVCF *bvcf)
452 VCFBackendSearchClosure *closure = g_new (VCFBackendSearchClosure, 1);
454 closure->bvcf = bvcf;
455 closure->view = book_view;
456 closure->thread = NULL;
457 closure->running = e_flag_new ();
459 g_object_set_data_full (G_OBJECT (book_view), "EBookBackendVCF.BookView::closure",
460 closure, (GDestroyNotify)closure_destroy);
465 static VCFBackendSearchClosure*
466 get_closure (EDataBookView *book_view)
468 return g_object_get_data (G_OBJECT (book_view), "EBookBackendVCF.BookView::closure");
472 book_view_thread (gpointer data)
474 EDataBookView *book_view = data;
475 VCFBackendSearchClosure *closure = get_closure (book_view);
479 /* ref the book view because it'll be removed and unrefed
480 when/if it's stopped */
481 bonobo_object_ref (book_view);
483 query = e_data_book_view_get_card_query (book_view);
485 if ( ! strcmp (query, "(contains \"x-evolution-any-field\" \"\")"))
486 e_data_book_view_notify_status_message (book_view, _("Loading..."));
488 e_data_book_view_notify_status_message (book_view, _("Searching..."));
490 d(printf ("signalling parent thread\n"));
491 e_flag_set (closure->running);
493 for (l = closure->bvcf->priv->contact_list; l; l = l->next) {
494 char *vcard_string = l->data;
495 EContact *contact = e_contact_new_from_vcard (vcard_string);
496 e_data_book_view_notify_update (closure->view, contact);
497 g_object_unref (contact);
499 if (!e_flag_is_set (closure->running))
503 if (e_flag_is_set (closure->running))
504 e_data_book_view_notify_complete (closure->view, GNOME_Evolution_Addressbook_Success);
506 /* unref the book view */
507 bonobo_object_unref (book_view);
509 d(printf ("finished initial population of book view\n"));
516 e_book_backend_vcf_start_book_view (EBookBackend *backend,
517 EDataBookView *book_view)
519 VCFBackendSearchClosure *closure = init_closure (book_view, E_BOOK_BACKEND_VCF (backend));
521 d(printf ("starting book view thread\n"));
522 closure->thread = g_thread_create (book_view_thread, book_view, TRUE, NULL);
524 e_flag_wait (closure->running);
526 /* at this point we know the book view thread is actually running */
527 d(printf ("returning from start_book_view\n"));
532 e_book_backend_vcf_stop_book_view (EBookBackend *backend,
533 EDataBookView *book_view)
535 VCFBackendSearchClosure *closure = get_closure (book_view);
538 d(printf ("stopping query\n"));
539 need_join = e_flag_is_set (closure->running);
540 e_flag_clear (closure->running);
543 g_thread_join (closure->thread);
547 e_book_backend_vcf_extract_path_from_uri (const char *uri)
549 g_assert (g_ascii_strncasecmp (uri, "vcf://", 6) == 0);
551 return g_strdup (uri + 6);
554 static EBookBackendSyncStatus
555 e_book_backend_vcf_authenticate_user (EBookBackendSync *backend,
560 const char *auth_method)
562 return GNOME_Evolution_Addressbook_Success;
565 static EBookBackendSyncStatus
566 e_book_backend_vcf_get_required_fields (EBookBackendSync *backend,
571 GList *fields = NULL;
573 fields = g_list_append (fields , g_strdup(e_contact_field_name (E_CONTACT_FILE_AS)));
574 *fields_out = fields;
575 return GNOME_Evolution_Addressbook_Success;
578 static EBookBackendSyncStatus
579 e_book_backend_vcf_get_supported_fields (EBookBackendSync *backend,
584 GList *fields = NULL;
587 /* XXX we need a way to say "we support everything", since the
589 for (i = 0; i < E_CONTACT_FIELD_LAST; i ++)
590 fields = g_list_append (fields, (char*)e_contact_field_name (i));
592 *fields_out = fields;
593 return GNOME_Evolution_Addressbook_Success;
596 #ifdef CREATE_DEFAULT_VCARD
597 # include <libedata-book/ximian-vcard.h>
600 static GNOME_Evolution_Addressbook_CallStatus
601 e_book_backend_vcf_load_source (EBookBackend *backend,
603 gboolean only_if_exists)
605 EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
607 gboolean writable = FALSE;
611 uri = e_source_get_uri (source);
613 dirname = e_book_backend_vcf_extract_path_from_uri (uri);
614 bvcf->priv->filename = g_build_filename (dirname, "addressbook.vcf", NULL);
616 fd = g_open (bvcf->priv->filename, O_RDWR | O_BINARY, 0);
618 bvcf->priv->contacts = g_hash_table_new (g_str_hash, g_str_equal);
623 fd = g_open (bvcf->priv->filename, O_RDONLY | O_BINARY, 0);
625 if (fd == -1 && !only_if_exists) {
628 /* the database didn't exist, so we create the
629 directory then the .vcf file */
630 rv = g_mkdir_with_parents (dirname, 0777);
631 if (rv == -1 && errno != EEXIST) {
632 g_warning ("failed to make directory %s: %s", dirname, strerror (errno));
633 if (errno == EACCES || errno == EPERM)
634 return GNOME_Evolution_Addressbook_PermissionDenied;
636 return GNOME_Evolution_Addressbook_OtherError;
639 fd = g_open (bvcf->priv->filename, O_CREAT | O_BINARY, 0666);
642 #ifdef CREATE_DEFAULT_VCARD
645 contact = do_create(bvcf, XIMIAN_VCARD, FALSE);
648 /* XXX check errors here */
649 g_object_unref (contact);
658 g_warning ("Failed to open addressbook at uri `%s'", uri);
659 g_warning ("error == %s", strerror(errno));
661 return GNOME_Evolution_Addressbook_OtherError;
664 load_file (bvcf, fd);
666 e_book_backend_set_is_loaded (backend, TRUE);
667 e_book_backend_set_is_writable (backend, writable);
670 return GNOME_Evolution_Addressbook_Success;
674 e_book_backend_vcf_get_static_capabilities (EBookBackend *backend)
676 return g_strdup("local,do-initial-query,contact-lists");
679 static GNOME_Evolution_Addressbook_CallStatus
680 e_book_backend_vcf_cancel_operation (EBookBackend *backend, EDataBook *book)
682 return GNOME_Evolution_Addressbook_CouldNotCancel;
686 e_book_backend_vcf_set_mode (EBookBackend *backend, int mode)
688 if (e_book_backend_is_loaded (backend)) {
689 e_book_backend_notify_writable (backend, TRUE);
690 e_book_backend_notify_connection_status (backend, TRUE);
695 e_book_backend_vcf_construct (EBookBackendVCF *backend)
697 g_assert (backend != NULL);
698 g_assert (E_IS_BOOK_BACKEND_VCF (backend));
700 if (! e_book_backend_construct (E_BOOK_BACKEND (backend)))
707 * e_book_backend_vcf_new:
710 e_book_backend_vcf_new (void)
712 EBookBackendVCF *backend;
714 backend = g_object_new (E_TYPE_BOOK_BACKEND_VCF, NULL);
716 if (! e_book_backend_vcf_construct (backend)) {
717 g_object_unref (backend);
722 return E_BOOK_BACKEND (backend);
726 e_book_backend_vcf_dispose (GObject *object)
728 EBookBackendVCF *bvcf;
730 bvcf = E_BOOK_BACKEND_VCF (object);
734 g_mutex_lock (bvcf->priv->mutex);
736 if (bvcf->priv->flush_timeout_tag) {
737 g_source_remove (bvcf->priv->flush_timeout_tag);
738 bvcf->priv->flush_timeout_tag = 0;
741 if (bvcf->priv->dirty)
744 g_hash_table_foreach (bvcf->priv->contacts, (GHFunc)g_free, NULL);
745 g_hash_table_destroy (bvcf->priv->contacts);
746 g_list_foreach (bvcf->priv->contact_list, (GFunc)g_free, NULL);
747 g_list_free (bvcf->priv->contact_list);
749 g_free (bvcf->priv->filename);
751 g_mutex_unlock (bvcf->priv->mutex);
753 g_mutex_free (bvcf->priv->mutex);
759 G_OBJECT_CLASS (e_book_backend_vcf_parent_class)->dispose (object);
763 e_book_backend_vcf_class_init (EBookBackendVCFClass *klass)
765 GObjectClass *object_class = G_OBJECT_CLASS (klass);
766 EBookBackendSyncClass *sync_class;
767 EBookBackendClass *backend_class;
769 e_book_backend_vcf_parent_class = g_type_class_peek_parent (klass);
771 sync_class = E_BOOK_BACKEND_SYNC_CLASS (klass);
772 backend_class = E_BOOK_BACKEND_CLASS (klass);
774 /* Set the virtual methods. */
775 backend_class->load_source = e_book_backend_vcf_load_source;
776 backend_class->get_static_capabilities = e_book_backend_vcf_get_static_capabilities;
777 backend_class->start_book_view = e_book_backend_vcf_start_book_view;
778 backend_class->stop_book_view = e_book_backend_vcf_stop_book_view;
779 backend_class->cancel_operation = e_book_backend_vcf_cancel_operation;
780 backend_class->set_mode = e_book_backend_vcf_set_mode;
781 sync_class->create_contact_sync = e_book_backend_vcf_create_contact;
782 sync_class->remove_contacts_sync = e_book_backend_vcf_remove_contacts;
783 sync_class->modify_contact_sync = e_book_backend_vcf_modify_contact;
784 sync_class->get_contact_sync = e_book_backend_vcf_get_contact;
785 sync_class->get_contact_list_sync = e_book_backend_vcf_get_contact_list;
786 sync_class->authenticate_user_sync = e_book_backend_vcf_authenticate_user;
787 sync_class->get_required_fields_sync = e_book_backend_vcf_get_required_fields;
788 sync_class->get_supported_fields_sync = e_book_backend_vcf_get_supported_fields;
790 object_class->dispose = e_book_backend_vcf_dispose;
794 e_book_backend_vcf_init (EBookBackendVCF *backend)
796 EBookBackendVCFPrivate *priv;
798 priv = g_new0 (EBookBackendVCFPrivate, 1);
799 priv->mutex = g_mutex_new();
801 backend->priv = priv;
805 * e_book_backend_vcf_get_type:
808 e_book_backend_vcf_get_type (void)
810 static GType type = 0;
814 sizeof (EBookBackendVCFClass),
815 NULL, /* base_class_init */
816 NULL, /* base_class_finalize */
817 (GClassInitFunc) e_book_backend_vcf_class_init,
818 NULL, /* class_finalize */
819 NULL, /* class_data */
820 sizeof (EBookBackendVCF),
822 (GInstanceInitFunc) e_book_backend_vcf_init
825 type = g_type_register_static (E_TYPE_BOOK_BACKEND_SYNC, "EBookBackendVCF", &info, 0);