Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / addressbook / backends / vcf / e-book-backend-vcf.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* e-book-backend-vcf.c - VCF contact backend.
4  *
5  * Copyright (C) 2005 Novell, Inc.
6  *
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.
10  *
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.
15  *
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.
20  *
21  * Authors: Chris Toshok <toshok@ximian.com>
22  */
23
24 #include <config.h>
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <time.h>
32 #include <sys/stat.h>
33 #include <sys/time.h>
34
35 #ifndef O_BINARY
36 #define O_BINARY 0
37 #endif
38
39 #include <glib.h>
40 #include <glib/gstdio.h>
41 #include <glib/gi18n-lib.h>
42
43 #include "libedataserver/e-data-server-util.h"
44 #include "libedataserver/e-flag.h"
45
46 #include "libebook/e-contact.h"
47  
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"
51
52 #include "e-book-backend-vcf.h"
53
54 #define PAS_ID_PREFIX "pas-id-"
55 #define FILE_FLUSH_TIMEOUT 5000
56
57 #define d(x) x
58
59 static EBookBackendSyncClass *e_book_backend_vcf_parent_class;
60 typedef struct _EBookBackendVCFBookView EBookBackendVCFBookView;
61 typedef struct _EBookBackendVCFSearchContext EBookBackendVCFSearchContext;
62
63 struct _EBookBackendVCFPrivate {
64         char       *filename;
65         GMutex     *mutex;
66         GHashTable *contacts;
67         GList      *contact_list;
68         gboolean    dirty;
69         int         flush_timeout_tag;
70         /* for future use */
71         void *reserved1;
72         void *reserved2;
73         void *reserved3;
74         void *reserved4;
75 };
76
77 static char *
78 e_book_backend_vcf_create_unique_id ()
79 {
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
82            should be okay. */
83         static guint c = 0;
84         return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", time(NULL), c++);
85 }
86
87 static void
88 insert_contact (EBookBackendVCF *vcf, char *vcard)
89 {
90         EContact *contact = e_contact_new_from_vcard (vcard);
91         char *id;
92
93         id = e_contact_get (contact, E_CONTACT_UID);
94         if (id) {
95                 char *vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
96
97                 vcf->priv->contact_list = g_list_prepend (vcf->priv->contact_list, vcard);
98
99                 g_hash_table_insert (vcf->priv->contacts,
100                                      id,
101                                      vcf->priv->contact_list);
102         }
103 }
104
105 static void
106 load_file (EBookBackendVCF *vcf, int fd)
107 {
108         FILE *fp;
109         GString *str;
110         char buf[1024];
111
112         fp = fdopen (fd, "rb");
113         if (!fp) {
114                 close (fd); /* callers depend on fd being closed by this function */
115                 g_warning ("failed to open `%s' for reading", vcf->priv->filename);
116                 return;
117         }
118
119         str = g_string_new ("");
120
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 */
124                         if (str->len) {
125                                 insert_contact (vcf, str->str);
126                                 g_string_assign (str, "");
127                         }
128                 }
129                 else {
130                         g_string_append (str, buf);
131                 }
132         }
133         if (str->len) {
134                 insert_contact (vcf, str->str);
135         }
136
137         g_string_free (str, TRUE);
138
139         fclose (fp);
140 }
141
142 static gboolean
143 save_file (EBookBackendVCF *vcf)
144 {
145         gboolean retv = FALSE;
146         GList *l;
147         char *new_path;
148         int fd, rv;
149
150         g_warning ("EBookBackendVCF flushing file to disk");
151
152         g_mutex_lock (vcf->priv->mutex);
153
154         new_path = g_strdup_printf ("%s.new", vcf->priv->filename);
155
156         fd = g_open (new_path, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666);
157         if (fd == -1) {
158                 g_warning ("write failed.  could not open output file\n");
159                 goto out;
160         }
161
162         for (l = vcf->priv->contact_list; l; l = l->next) {
163                 char *vcard_str = l->data;
164                 int len = strlen (vcard_str);
165
166                 rv = write (fd, vcard_str, len);
167
168                 if (rv < len) {
169                         /* XXX */
170                         g_warning ("write failed.  we need to handle short writes\n");
171                         g_unlink (new_path);
172                         goto out;
173                 }
174
175                 rv = write (fd, "\r\n\r\n", 4);
176                 if (rv < 4) {
177                         /* XXX */
178                         g_warning ("write failed.  we need to handle short writes\n");
179                         g_unlink (new_path);
180                         goto out;
181                 }
182         }
183
184         if (0 > g_rename (new_path, vcf->priv->filename)) {
185                 g_warning ("Failed to rename %s: %s\n", vcf->priv->filename, strerror(errno));
186                 g_unlink (new_path);
187                 goto out;
188         }
189         retv = TRUE;
190
191 out:
192         if (fd != -1)
193                 close (fd);
194         g_free (new_path);
195         vcf->priv->dirty = !retv;
196         g_mutex_unlock (vcf->priv->mutex);
197
198         return retv;
199 }
200
201 static gboolean
202 vcf_flush_file (gpointer data)
203 {
204         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (data);
205
206         if (!bvcf->priv->dirty) {
207                 bvcf->priv->flush_timeout_tag = 0;
208                 return FALSE;
209         }
210
211         if (!save_file (bvcf)) {
212                 g_warning ("failed to flush the .vcf file to disk, will try again next timeout");
213                 return TRUE;
214         }
215
216         bvcf->priv->flush_timeout_tag = 0;
217         return FALSE;
218 }
219
220 static void
221 set_revision (EContact *contact)
222 {
223         char time_string[25] = {0};
224         const struct tm *tm = NULL;
225         GTimeVal tv;
226
227         g_get_current_time (&tv);
228         tm = gmtime (&tv.tv_sec);
229         if (tm)
230                 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", tm);
231         e_contact_set (contact, E_CONTACT_REV, time_string);
232 }
233
234 static EContact *
235 do_create(EBookBackendVCF  *bvcf,
236           const char     *vcard_req,
237           gboolean        dirty_the_file)
238 {
239         char           *id;
240         EContact       *contact;
241         char           *vcard;
242         const char     *rev;
243
244         /* at the very least we need the unique_id generation to be
245            protected by the lock, even if the actual vcard parsing
246            isn't. */
247         g_mutex_lock (bvcf->priv->mutex);
248         id = e_book_backend_vcf_create_unique_id ();
249
250         contact = e_contact_new_from_vcard (vcard_req);
251         e_contact_set(contact, E_CONTACT_UID, id);
252         g_free (id);
253
254         rev = e_contact_get_const (contact,  E_CONTACT_REV);
255         if (!(rev && *rev))
256                 set_revision (contact);
257
258         vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
259
260         insert_contact (bvcf, vcard);
261
262         if (dirty_the_file) {
263                 bvcf->priv->dirty = TRUE;
264
265                 if (!bvcf->priv->flush_timeout_tag)
266                         bvcf->priv->flush_timeout_tag = g_timeout_add (FILE_FLUSH_TIMEOUT,
267                                                                        vcf_flush_file, bvcf);
268         }
269
270         g_mutex_unlock (bvcf->priv->mutex);
271
272         return contact;
273 }
274
275 static EBookBackendSyncStatus
276 e_book_backend_vcf_create_contact (EBookBackendSync *backend,
277                                    EDataBook *book,
278                                    guint32 opid,
279                                    const char *vcard,
280                                    EContact **contact)
281 {
282         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
283
284         *contact = do_create(bvcf, vcard, TRUE);
285         if (*contact) {
286                 return GNOME_Evolution_Addressbook_Success;
287         }
288         else {
289                 /* XXX need a different call status for this case, i
290                    think */
291                 return GNOME_Evolution_Addressbook_ContactNotFound;
292         }
293 }
294
295 static EBookBackendSyncStatus
296 e_book_backend_vcf_remove_contacts (EBookBackendSync *backend,
297                                     EDataBook *book,
298                                     guint32 opid,
299                                     GList *id_list,
300                                     GList **ids)
301 {
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;
305         GList *elem;
306         gboolean success;
307         char *real_id;
308
309         g_mutex_lock (bvcf->priv->mutex);
310         success = g_hash_table_lookup_extended (bvcf->priv->contacts, id, (gpointer)&real_id, (gpointer)&elem);
311         if (!success) {
312                 g_mutex_unlock (bvcf->priv->mutex);
313                 return GNOME_Evolution_Addressbook_ContactNotFound;
314         }
315
316         success = g_hash_table_remove (bvcf->priv->contacts, real_id);
317         if (!success) {
318                 g_mutex_unlock (bvcf->priv->mutex);
319                 return GNOME_Evolution_Addressbook_ContactNotFound;
320         }
321
322         g_free (real_id);
323         g_free (elem->data);
324         bvcf->priv->contact_list = g_list_remove_link (bvcf->priv->contact_list, elem);
325
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);
331
332         *ids = g_list_append (*ids, id);
333
334         return GNOME_Evolution_Addressbook_Success;
335 }
336
337 static EBookBackendSyncStatus
338 e_book_backend_vcf_modify_contact (EBookBackendSync *backend,
339                                    EDataBook *book,
340                                    guint32 opid,
341                                    const char *vcard,
342                                    EContact **contact)
343 {
344         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
345         char *old_id;
346         GList *elem;
347         const char *id;
348         gboolean success;
349
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);
353
354         g_mutex_lock (bvcf->priv->mutex);
355         success = g_hash_table_lookup_extended (bvcf->priv->contacts, id, (gpointer)&old_id, (gpointer)&elem);
356
357         if (!success) {
358                 g_mutex_unlock (bvcf->priv->mutex);
359                 return GNOME_Evolution_Addressbook_ContactNotFound;
360         }
361
362         g_free (elem->data);
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);
369
370         return GNOME_Evolution_Addressbook_Success;
371 }
372
373 static EBookBackendSyncStatus
374 e_book_backend_vcf_get_contact (EBookBackendSync *backend,
375                                 EDataBook *book,
376                                 guint32 opid,
377                                 const char *id,
378                                 char **vcard)
379 {
380         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
381         GList *elem;
382
383         elem = g_hash_table_lookup (bvcf->priv->contacts, id);
384
385         if (elem) {
386                 *vcard = g_strdup (elem->data);
387                 return GNOME_Evolution_Addressbook_Success;
388         } else {
389                 *vcard = g_strdup ("");
390                 return GNOME_Evolution_Addressbook_ContactNotFound;
391         }
392 }
393
394
395 typedef struct {
396         EBookBackendVCF      *bvcf;
397         gboolean            search_needed;
398         EBookBackendSExp *card_sexp;
399         GList              *list;
400 } GetContactListClosure;
401
402 static void
403 foreach_get_contact_compare (char *vcard_string, GetContactListClosure *closure)
404 {
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));
407         }
408 }
409
410 static EBookBackendSyncStatus
411 e_book_backend_vcf_get_contact_list (EBookBackendSync *backend,
412                                      EDataBook *book,
413                                      guint32 opid,
414                                      const char *query,
415                                      GList **contacts)
416 {
417         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
418         const char *search = query;
419         GetContactListClosure closure;
420
421         closure.bvcf = bvcf;
422         closure.search_needed = strcmp (search, "(contains \"x-evolution-any-field\" \"\")");
423         closure.card_sexp = e_book_backend_sexp_new (search);
424         closure.list = NULL;
425
426         g_list_foreach (bvcf->priv->contact_list, (GFunc)foreach_get_contact_compare, &closure);
427
428         g_object_unref (closure.card_sexp);
429
430         *contacts = closure.list;
431         return GNOME_Evolution_Addressbook_Success;
432 }
433
434 typedef struct {
435         EBookBackendVCF *bvcf;
436         EDataBookView *view;
437         GThread *thread;
438         EFlag *running;
439 } VCFBackendSearchClosure;
440
441 static void
442 closure_destroy (VCFBackendSearchClosure *closure)
443 {
444         d(printf ("destroying search closure\n"));
445         e_flag_free (closure->running);
446         g_free (closure);
447 }
448
449 static VCFBackendSearchClosure*
450 init_closure (EDataBookView *book_view, EBookBackendVCF *bvcf)
451 {
452         VCFBackendSearchClosure *closure = g_new (VCFBackendSearchClosure, 1);
453
454         closure->bvcf = bvcf;
455         closure->view = book_view;
456         closure->thread = NULL;
457         closure->running = e_flag_new ();
458
459         g_object_set_data_full (G_OBJECT (book_view), "EBookBackendVCF.BookView::closure",
460                                 closure, (GDestroyNotify)closure_destroy);
461
462         return closure;
463 }
464
465 static VCFBackendSearchClosure*
466 get_closure (EDataBookView *book_view)
467 {
468         return g_object_get_data (G_OBJECT (book_view), "EBookBackendVCF.BookView::closure");
469 }
470
471 static gpointer
472 book_view_thread (gpointer data)
473 {
474         EDataBookView *book_view = data;
475         VCFBackendSearchClosure *closure = get_closure (book_view);
476         const char *query;
477         GList *l;
478
479         /* ref the book view because it'll be removed and unrefed
480            when/if it's stopped */
481         bonobo_object_ref (book_view);
482
483         query = e_data_book_view_get_card_query (book_view);
484
485         if ( ! strcmp (query, "(contains \"x-evolution-any-field\" \"\")"))
486                 e_data_book_view_notify_status_message (book_view, _("Loading..."));
487         else
488                 e_data_book_view_notify_status_message (book_view, _("Searching..."));
489
490         d(printf ("signalling parent thread\n"));
491         e_flag_set (closure->running);
492
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);
498
499                 if (!e_flag_is_set (closure->running))
500                         break;
501         }
502
503         if (e_flag_is_set (closure->running))
504                 e_data_book_view_notify_complete (closure->view, GNOME_Evolution_Addressbook_Success);
505
506         /* unref the book view */
507         bonobo_object_unref (book_view);
508
509         d(printf ("finished initial population of book view\n"));
510
511         return NULL;
512 }
513
514
515 static void
516 e_book_backend_vcf_start_book_view (EBookBackend  *backend,
517                                     EDataBookView *book_view)
518 {
519         VCFBackendSearchClosure *closure = init_closure (book_view, E_BOOK_BACKEND_VCF (backend));
520
521         d(printf ("starting book view thread\n"));
522         closure->thread = g_thread_create (book_view_thread, book_view, TRUE, NULL);
523
524         e_flag_wait (closure->running);
525
526         /* at this point we know the book view thread is actually running */
527         d(printf ("returning from start_book_view\n"));
528
529 }
530
531 static void
532 e_book_backend_vcf_stop_book_view (EBookBackend  *backend,
533                                    EDataBookView *book_view)
534 {
535         VCFBackendSearchClosure *closure = get_closure (book_view);
536         gboolean need_join;
537
538         d(printf ("stopping query\n"));
539         need_join = e_flag_is_set (closure->running);
540         e_flag_clear (closure->running);
541
542         if (need_join)
543                 g_thread_join (closure->thread);
544 }
545
546 static char *
547 e_book_backend_vcf_extract_path_from_uri (const char *uri)
548 {
549         g_assert (g_ascii_strncasecmp (uri, "vcf://", 6) == 0);
550
551         return g_strdup (uri + 6);
552 }
553
554 static EBookBackendSyncStatus
555 e_book_backend_vcf_authenticate_user (EBookBackendSync *backend,
556                                       EDataBook *book,
557                                       guint32 opid,
558                                       const char *user,
559                                       const char *passwd,
560                                       const char *auth_method)
561 {
562         return GNOME_Evolution_Addressbook_Success;
563 }
564
565 static EBookBackendSyncStatus
566 e_book_backend_vcf_get_required_fields (EBookBackendSync *backend,
567                                          EDataBook *book,
568                                          guint32 opid,
569                                          GList **fields_out)
570 {
571         GList *fields = NULL;
572
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;
576 }
577
578 static EBookBackendSyncStatus
579 e_book_backend_vcf_get_supported_fields (EBookBackendSync *backend,
580                                          EDataBook *book,
581                                          guint32 opid,
582                                          GList **fields_out)
583 {
584         GList *fields = NULL;
585         int i;
586
587         /* XXX we need a way to say "we support everything", since the
588            vcf backend does */
589         for (i = 0; i < E_CONTACT_FIELD_LAST; i ++)
590                 fields = g_list_append (fields, (char*)e_contact_field_name (i));               
591
592         *fields_out = fields;
593         return GNOME_Evolution_Addressbook_Success;
594 }
595
596 #ifdef CREATE_DEFAULT_VCARD
597 # include <libedata-book/ximian-vcard.h>
598 #endif
599
600 static GNOME_Evolution_Addressbook_CallStatus
601 e_book_backend_vcf_load_source (EBookBackend             *backend,
602                                 ESource                  *source,
603                                 gboolean                  only_if_exists)
604 {
605         EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
606         char           *dirname;
607         gboolean        writable = FALSE;
608         gchar          *uri;
609         int fd;
610
611         uri = e_source_get_uri (source);
612
613         dirname = e_book_backend_vcf_extract_path_from_uri (uri);
614         bvcf->priv->filename = g_build_filename (dirname, "addressbook.vcf", NULL);
615
616         fd = g_open (bvcf->priv->filename, O_RDWR | O_BINARY, 0);
617
618         bvcf->priv->contacts = g_hash_table_new (g_str_hash, g_str_equal);
619
620         if (fd != -1) {
621                 writable = TRUE;
622         } else {
623                 fd = g_open (bvcf->priv->filename, O_RDONLY | O_BINARY, 0);
624
625                 if (fd == -1 && !only_if_exists) {
626                         int rv;
627
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;
635                                 else
636                                         return GNOME_Evolution_Addressbook_OtherError;
637                         }
638
639                         fd = g_open (bvcf->priv->filename, O_CREAT | O_BINARY, 0666);
640
641                         if (fd != -1) {
642 #ifdef CREATE_DEFAULT_VCARD
643                                 EContact *contact;
644
645                                 contact = do_create(bvcf, XIMIAN_VCARD, FALSE);
646                                 save_file (bvcf);
647
648                                 /* XXX check errors here */
649                                 g_object_unref (contact);
650 #endif
651
652                                 writable = TRUE;
653                         }
654                 }
655         }
656
657         if (fd == -1) {
658                 g_warning ("Failed to open addressbook at uri `%s'", uri);
659                 g_warning ("error == %s", strerror(errno));
660                 g_free (uri);
661                 return GNOME_Evolution_Addressbook_OtherError;
662         }
663
664         load_file (bvcf, fd);
665
666         e_book_backend_set_is_loaded (backend, TRUE);
667         e_book_backend_set_is_writable (backend, writable);
668
669         g_free (uri);
670         return GNOME_Evolution_Addressbook_Success;
671 }
672
673 static char *
674 e_book_backend_vcf_get_static_capabilities (EBookBackend *backend)
675 {
676         return g_strdup("local,do-initial-query,contact-lists");
677 }
678
679 static GNOME_Evolution_Addressbook_CallStatus
680 e_book_backend_vcf_cancel_operation (EBookBackend *backend, EDataBook *book)
681 {
682         return GNOME_Evolution_Addressbook_CouldNotCancel;
683 }
684
685 static void 
686 e_book_backend_vcf_set_mode (EBookBackend *backend, int mode)
687 {
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);
691         }
692 }
693
694 static gboolean
695 e_book_backend_vcf_construct (EBookBackendVCF *backend)
696 {
697         g_assert (backend != NULL);
698         g_assert (E_IS_BOOK_BACKEND_VCF (backend));
699
700         if (! e_book_backend_construct (E_BOOK_BACKEND (backend)))
701                 return FALSE;
702
703         return TRUE;
704 }
705
706 /**
707  * e_book_backend_vcf_new:
708  */
709 EBookBackend *
710 e_book_backend_vcf_new (void)
711 {
712         EBookBackendVCF *backend;
713
714         backend = g_object_new (E_TYPE_BOOK_BACKEND_VCF, NULL);
715
716         if (! e_book_backend_vcf_construct (backend)) {
717                 g_object_unref (backend);
718
719                 return NULL;
720         }
721
722         return E_BOOK_BACKEND (backend);
723 }
724
725 static void
726 e_book_backend_vcf_dispose (GObject *object)
727 {
728         EBookBackendVCF *bvcf;
729
730         bvcf = E_BOOK_BACKEND_VCF (object);
731
732         if (bvcf->priv) {
733
734                 g_mutex_lock (bvcf->priv->mutex);
735
736                 if (bvcf->priv->flush_timeout_tag) {
737                         g_source_remove (bvcf->priv->flush_timeout_tag);
738                         bvcf->priv->flush_timeout_tag = 0;
739                 }
740
741                 if (bvcf->priv->dirty)
742                         save_file (bvcf);
743
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);
748
749                 g_free (bvcf->priv->filename);
750
751                 g_mutex_unlock (bvcf->priv->mutex);
752
753                 g_mutex_free (bvcf->priv->mutex);
754
755                 g_free (bvcf->priv);
756                 bvcf->priv = NULL;
757         }
758
759         G_OBJECT_CLASS (e_book_backend_vcf_parent_class)->dispose (object);     
760 }
761
762 static void
763 e_book_backend_vcf_class_init (EBookBackendVCFClass *klass)
764 {
765         GObjectClass    *object_class = G_OBJECT_CLASS (klass);
766         EBookBackendSyncClass *sync_class;
767         EBookBackendClass *backend_class;
768
769         e_book_backend_vcf_parent_class = g_type_class_peek_parent (klass);
770
771         sync_class = E_BOOK_BACKEND_SYNC_CLASS (klass);
772         backend_class = E_BOOK_BACKEND_CLASS (klass);
773
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;
789
790         object_class->dispose = e_book_backend_vcf_dispose;
791 }
792
793 static void
794 e_book_backend_vcf_init (EBookBackendVCF *backend)
795 {
796         EBookBackendVCFPrivate *priv;
797
798         priv                 = g_new0 (EBookBackendVCFPrivate, 1);
799         priv->mutex = g_mutex_new();
800
801         backend->priv = priv;
802 }
803
804 /**
805  * e_book_backend_vcf_get_type:
806  */
807 GType
808 e_book_backend_vcf_get_type (void)
809 {
810         static GType type = 0;
811
812         if (! type) {
813                 GTypeInfo info = {
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),
821                         0,    /* n_preallocs */
822                         (GInstanceInitFunc) e_book_backend_vcf_init
823                 };
824
825                 type = g_type_register_static (E_TYPE_BOOK_BACKEND_SYNC, "EBookBackendVCF", &info, 0);
826         }
827
828         return type;
829 }