Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / addressbook / backends / file / e-book-backend-file.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* e-book-backend-file.c - File 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: Nat Friedman <nat@novell.com>
22  *          Chris Toshok <toshok@ximian.com>
23  *          Hans Petter Jansson <hpj@novell.com>
24  */
25
26 #include <config.h> 
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <time.h>
34 #include <errno.h>
35 #include "db.h"
36 #include <sys/stat.h>
37 #include <sys/time.h>
38
39 #include <glib.h>
40 #include <glib/gstdio.h>
41 #include <glib/gi18n-lib.h>
42
43 #include "libedataserver/e-dbhash.h"
44 #include "libedataserver/e-db3-utils.h"
45 #include "libedataserver/e-data-server-util.h"
46 #include "libedataserver/e-flag.h"
47
48 #include "libebook/e-contact.h"
49
50 #include "libedata-book/e-book-backend-sexp.h"
51 #include "libedata-book/e-book-backend-summary.h"
52 #include "libedata-book/e-data-book.h"
53 #include "libedata-book/e-data-book-view.h"
54
55 #include "e-book-backend-file.h"
56
57 #define d(x)
58
59 #define CHANGES_DB_SUFFIX ".changes.db"
60
61 #define E_BOOK_BACKEND_FILE_VERSION_NAME "PAS-DB-VERSION"
62 #define E_BOOK_BACKEND_FILE_VERSION "0.2"
63
64 #define PAS_ID_PREFIX "pas-id-"
65 #define SUMMARY_FLUSH_TIMEOUT 5000
66
67 static EBookBackendSyncClass *e_book_backend_file_parent_class;
68
69 struct _EBookBackendFilePrivate {
70         char     *dirname;
71         char     *filename;
72         char     *summary_filename;
73         DB       *file_db;
74         DB_ENV   *env;
75         EBookBackendSummary *summary;
76         /* for future use */
77         void *reserved1;
78         void *reserved2;
79         void *reserved3;
80         void *reserved4;
81 };
82
83 static GStaticMutex global_env_lock = G_STATIC_MUTEX_INIT;
84 static struct {
85         int ref_count;
86         DB_ENV *env;
87 } global_env;
88
89 static EBookBackendSyncStatus
90 db_error_to_status (const int db_error)
91 {
92         switch (db_error) {
93         case 0:
94                 return GNOME_Evolution_Addressbook_Success;
95         case DB_NOTFOUND:
96                 return GNOME_Evolution_Addressbook_ContactNotFound;
97         case EACCES:
98                 return GNOME_Evolution_Addressbook_PermissionDenied;
99         default:
100                 return GNOME_Evolution_Addressbook_OtherError;
101         }
102 }
103
104 static void
105 string_to_dbt(const char *str, DBT *dbt)
106 {
107         memset (dbt, 0, sizeof (*dbt));
108         dbt->data = (void*)str;
109         dbt->size = strlen (str) + 1;
110         dbt->flags = DB_DBT_USERMEM;
111 }
112
113 static EContact*
114 create_contact (char *uid, const char *vcard)
115 {
116         EContact *contact = e_contact_new_from_vcard (vcard);
117         if (!e_contact_get_const (contact, E_CONTACT_UID))
118                 e_contact_set (contact, E_CONTACT_UID, uid);
119
120         return contact;
121 }
122
123 static void
124 build_summary (EBookBackendFilePrivate *bfpriv)
125 {
126         DB             *db = bfpriv->file_db;
127         DBC            *dbc;
128         int            db_error;
129         DBT  id_dbt, vcard_dbt;
130
131         db_error = db->cursor (db, NULL, &dbc, 0);
132
133         if (db_error != 0) {
134                 g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
135                 return;
136         }
137
138         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
139         memset (&id_dbt, 0, sizeof (id_dbt));
140         db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
141
142         while (db_error == 0) {
143
144                 /* don't include the version in the list of cards */
145                 if (id_dbt.size != strlen(E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
146                     || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
147                         EContact *contact = create_contact (id_dbt.data, vcard_dbt.data);
148                         e_book_backend_summary_add_contact (bfpriv->summary, contact);
149                         g_object_unref (contact);
150                 }
151
152                 db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
153
154         }
155
156         dbc->c_close (dbc);
157 }
158
159 static char *
160 e_book_backend_file_create_unique_id (void)
161 {
162         /* use a 32 counter and the 32 bit timestamp to make an id.
163            it's doubtful 2^32 id's will be created in a second, so we
164            should be okay. */
165         static guint c = 0;
166         return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", time(NULL), c++);
167 }
168
169 static void 
170 set_revision (EContact *contact)
171 {
172         char time_string[25] = {0};
173         const struct tm *tm = NULL;
174         GTimeVal tv;
175
176         g_get_current_time (&tv);
177         tm = gmtime (&tv.tv_sec);
178         if (tm)
179                 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", tm);
180         e_contact_set (contact, E_CONTACT_REV, time_string);
181         
182 }
183
184 static EBookBackendSyncStatus
185 do_create(EBookBackendFile  *bf,
186           const char      *vcard_req,
187           EContact **contact)
188 {
189         DB             *db = bf->priv->file_db;
190         DBT            id_dbt, vcard_dbt;
191         int            db_error;
192         char           *id;
193         char           *vcard;
194         const char *rev;
195         
196         g_assert (bf);
197         g_assert (vcard_req);
198         g_assert (contact);
199
200         id = e_book_backend_file_create_unique_id ();
201
202         string_to_dbt (id, &id_dbt);
203
204         *contact = e_contact_new_from_vcard (vcard_req);
205         e_contact_set (*contact, E_CONTACT_UID, id);
206         rev = e_contact_get_const (*contact,  E_CONTACT_REV);
207         if (!(rev && *rev))
208                 set_revision (*contact);
209
210         vcard = e_vcard_to_string (E_VCARD (*contact), EVC_FORMAT_VCARD_30);
211
212         string_to_dbt (vcard, &vcard_dbt);
213
214         db_error = db->put (db, NULL, &id_dbt, &vcard_dbt, 0);
215
216         g_free (vcard);
217
218         if (0 == db_error) {
219                 db_error = db->sync (db, 0);
220                 if (db_error != 0) {
221                         g_warning ("db->sync failed with %s", db_strerror (db_error));
222                 }
223         } else {
224                 g_warning (G_STRLOC ": db->put failed with %s", db_strerror (db_error));
225                 g_object_unref (*contact);
226                 *contact = NULL;
227         }
228
229         g_free (id);
230         return db_error_to_status (db_error);
231 }
232
233 static EBookBackendSyncStatus
234 e_book_backend_file_create_contact (EBookBackendSync *backend,
235                                     EDataBook *book,
236                                     guint32 opid,
237                                     const char *vcard,
238                                     EContact **contact)
239 {
240         EBookBackendSyncStatus status;
241         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
242
243         status = do_create (bf, vcard, contact);
244         if (status == GNOME_Evolution_Addressbook_Success) {
245                 e_book_backend_summary_add_contact (bf->priv->summary, *contact);
246         }
247         return status;
248 }
249
250 static EBookBackendSyncStatus
251 e_book_backend_file_remove_contacts (EBookBackendSync *backend,
252                                      EDataBook *book,
253                                      guint32 opid,
254                                      GList *id_list,
255                                      GList **ids)
256 {
257         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
258         DB             *db = bf->priv->file_db;
259         DBT            id_dbt;
260         int            db_error;
261         char          *id;
262         GList         *l;
263         GList         *removed_cards = NULL;
264         GNOME_Evolution_Addressbook_CallStatus rv = GNOME_Evolution_Addressbook_Success;
265
266         for (l = id_list; l; l = l->next) {
267                 id = l->data;
268
269                 string_to_dbt (id, &id_dbt);
270
271                 db_error = db->del (db, NULL, &id_dbt, 0);
272                 if (0 != db_error) {
273                         g_warning (G_STRLOC ": db->del failed with %s", db_strerror (db_error));
274                         rv = db_error_to_status (db_error);
275                         continue;
276                 }
277
278                 removed_cards = g_list_prepend (removed_cards, id);
279         }
280
281         /* if we actually removed some, try to sync */
282         if (removed_cards) {
283                 db_error = db->sync (db, 0);
284                 if (db_error != 0)
285                         g_warning (G_STRLOC ": db->sync failed with %s", db_strerror (db_error));
286         }
287
288         *ids = removed_cards;
289
290         for (l = removed_cards; l; l = l->next) {
291                 char *id = l->data;
292                 e_book_backend_summary_remove_contact (bf->priv->summary, id);
293         }
294
295         return rv;
296 }
297
298 static EBookBackendSyncStatus
299 e_book_backend_file_modify_contact (EBookBackendSync *backend,
300                                     EDataBook *book,
301                                     guint32 opid,
302                                     const char *vcard,
303                                     EContact **contact)
304 {
305         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
306         DB             *db = bf->priv->file_db;
307         DBT            id_dbt, vcard_dbt;
308         int            db_error;
309         const char    *id, *lookup_id;
310         char          *vcard_with_rev;
311
312         *contact = e_contact_new_from_vcard (vcard);
313         id = e_contact_get_const (*contact, E_CONTACT_UID);
314
315         if (id == NULL)
316                 return GNOME_Evolution_Addressbook_OtherError;
317
318         /* update the revision (modified time of contact) */
319         set_revision (*contact);
320         vcard_with_rev = e_vcard_to_string (E_VCARD (*contact), EVC_FORMAT_VCARD_30);
321
322         /* This is disgusting, but for a time cards were added with
323            ID's that are no longer used (they contained both the uri
324            and the id.) If we recognize it as a uri (file:///...) trim
325            off everything before the last '/', and use that as the
326            id.*/
327         if (!strncmp (id, "file:///", strlen ("file:///"))) {
328                 lookup_id = strrchr (id, '/') + 1;
329         }
330         else
331                 lookup_id = id;
332
333         string_to_dbt (lookup_id, &id_dbt);
334         string_to_dbt (vcard_with_rev, &vcard_dbt);
335
336         db_error = db->put (db, NULL, &id_dbt, &vcard_dbt, 0);
337
338         if (0 == db_error) {
339                 db_error = db->sync (db, 0);
340                 if (db_error != 0) {
341                         g_warning (G_STRLOC ": db->sync failed with %s", db_strerror (db_error));
342                 } else {
343                         e_book_backend_summary_remove_contact (bf->priv->summary, id);
344                         e_book_backend_summary_add_contact (bf->priv->summary, *contact);
345                 }
346         } else {
347                 g_warning (G_STRLOC ": db->put failed with %s", db_strerror(db_error));
348         }
349         g_free (vcard_with_rev);
350
351         if (0 == db_error)
352                 return GNOME_Evolution_Addressbook_Success;
353         else
354                 return db_error_to_status (db_error);
355 }
356
357 static EBookBackendSyncStatus
358 e_book_backend_file_get_contact (EBookBackendSync *backend,
359                                  EDataBook *book,
360                                  guint32 opid,
361                                  const char *id,
362                                  char **vcard)
363 {
364         EBookBackendFile *bf;
365         DB             *db;
366         DBT             id_dbt, vcard_dbt;
367         int             db_error = 0;
368
369         bf = E_BOOK_BACKEND_FILE (backend);
370         db = bf->priv->file_db;
371
372         string_to_dbt (id, &id_dbt);
373         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
374         vcard_dbt.flags = DB_DBT_MALLOC;
375
376         db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
377
378         if (db_error == 0) {
379                 *vcard = vcard_dbt.data;
380                 return GNOME_Evolution_Addressbook_Success;
381         } else {
382                 g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
383                 *vcard = g_strdup ("");
384                 return GNOME_Evolution_Addressbook_ContactNotFound;
385         }
386 }
387
388 static EBookBackendSyncStatus
389 e_book_backend_file_get_contact_list (EBookBackendSync *backend,
390                                       EDataBook *book,
391                                       guint32 opid,
392                                       const char *query,
393                                       GList **contacts)
394 {
395         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
396         DB             *db = bf->priv->file_db;
397         DBC            *dbc;
398         int            db_error;
399         DBT  id_dbt, vcard_dbt;
400         EBookBackendSExp *card_sexp = NULL;
401         gboolean search_needed;
402         const char *search = query;
403         GList *contact_list = NULL;
404         EBookBackendSyncStatus status;
405         
406         d(printf ("e_book_backend_file_get_contact_list (%s)\n", search));
407         status = GNOME_Evolution_Addressbook_Success;
408         if (e_book_backend_summary_is_summary_query (bf->priv->summary, search)) {
409         
410                 /* do a summary query */
411                 GPtrArray *ids = e_book_backend_summary_search (bf->priv->summary, search);
412                 int i;
413
414                 for (i = 0; i < ids->len; i ++) {
415                         char *id = g_ptr_array_index (ids, i);
416                         string_to_dbt (id, &id_dbt);
417                         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
418                         vcard_dbt.flags = DB_DBT_MALLOC;
419
420                         db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
421                         if (db_error == 0) {
422                                 contact_list = g_list_prepend (contact_list, vcard_dbt.data);
423                         } else {
424                                 g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
425                                 status = db_error_to_status (db_error);
426                                 break;
427                         }
428                 }
429                 g_ptr_array_free (ids, TRUE);
430         } else {
431                 search_needed = TRUE;
432                 if (!strcmp (search, "(contains \"x-evolution-any-field\" \"\")"))
433                         search_needed = FALSE;
434
435                 card_sexp = e_book_backend_sexp_new (search);
436                 if (!card_sexp) {
437                         /* XXX this needs to be an invalid query error of some sort*/
438                         return GNOME_Evolution_Addressbook_OtherError;
439                 }
440
441                 db_error = db->cursor (db, NULL, &dbc, 0);
442
443                 if (db_error != 0) {
444                         g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
445                         /* XXX this needs to be some CouldNotOpen error */
446                         return db_error_to_status (db_error);
447                 }
448
449                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
450                 vcard_dbt.flags = DB_DBT_MALLOC;
451                 memset (&id_dbt, 0, sizeof (id_dbt));
452                 db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
453
454                 while (db_error == 0) {
455
456                         /* don't include the version in the list of cards */
457                         if (id_dbt.size != strlen(E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
458                             || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
459
460                                 if ((!search_needed) || (card_sexp != NULL && e_book_backend_sexp_match_vcard  (card_sexp, vcard_dbt.data))) {
461                                         contact_list = g_list_prepend (contact_list, vcard_dbt.data);
462                                 }
463                         }
464
465                         db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
466
467                 }
468                 g_object_unref (card_sexp);
469                 
470                 if (db_error == DB_NOTFOUND) {
471                         status = GNOME_Evolution_Addressbook_Success;
472                 } else {
473                         g_warning (G_STRLOC ": dbc->c_get failed with %s", db_strerror (db_error));
474                         status = db_error_to_status (db_error);
475                 }
476
477                 db_error = dbc->c_close(dbc);
478                 if (db_error != 0) {
479                         g_warning (G_STRLOC ": dbc->c_close failed with %s", db_strerror (db_error));
480                 }
481         }
482
483         *contacts = contact_list;
484         return status;
485 }
486
487 typedef struct {
488         EBookBackendFile *bf;
489         GThread *thread;
490         EFlag *running;
491 } FileBackendSearchClosure;
492
493 static void
494 closure_destroy (FileBackendSearchClosure *closure)
495 {
496         d(printf ("destroying search closure\n"));
497         e_flag_free (closure->running);
498         g_free (closure);
499 }
500
501 static FileBackendSearchClosure*
502 init_closure (EDataBookView *book_view, EBookBackendFile *bf)
503 {
504         FileBackendSearchClosure *closure = g_new (FileBackendSearchClosure, 1);
505
506         closure->bf = bf;
507         closure->thread = NULL;
508         closure->running = e_flag_new ();
509
510         g_object_set_data_full (G_OBJECT (book_view), "EBookBackendFile.BookView::closure",
511                                 closure, (GDestroyNotify)closure_destroy);
512
513         return closure;
514 }
515
516 static FileBackendSearchClosure*
517 get_closure (EDataBookView *book_view)
518 {
519         return g_object_get_data (G_OBJECT (book_view), "EBookBackendFile.BookView::closure");
520 }
521
522 static gpointer
523 book_view_thread (gpointer data)
524 {
525         EDataBookView *book_view;
526         FileBackendSearchClosure *closure;
527         EBookBackendFile *bf;
528         const char *query;
529         DB  *db;
530         DBT id_dbt, vcard_dbt;
531         int db_error;
532         gboolean allcontacts;
533
534         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (data), NULL);
535
536         book_view = data;
537         closure = get_closure (book_view);
538         if (!closure) {
539                 g_warning (G_STRLOC ": NULL closure in book view thread");
540                 return NULL;
541         }
542         bf = closure->bf;
543
544         d(printf ("starting initial population of book view\n"));
545
546         /* ref the book view because it'll be removed and unrefed
547            when/if it's stopped */
548         bonobo_object_ref (book_view);
549         
550         db = bf->priv->file_db;
551         query = e_data_book_view_get_card_query (book_view);
552
553         if ( ! strcmp (query, "(contains \"x-evolution-any-field\" \"\")")) {
554                 e_data_book_view_notify_status_message (book_view, _("Loading..."));
555                 allcontacts = TRUE;
556         } else {
557                 e_data_book_view_notify_status_message (book_view, _("Searching..."));
558                 allcontacts = FALSE;
559         }
560
561         d(printf ("signalling parent thread\n"));
562         e_flag_set (closure->running);
563
564         if (e_book_backend_summary_is_summary_query (bf->priv->summary, query)) {
565                 /* do a summary query */
566                 GPtrArray *ids = e_book_backend_summary_search (bf->priv->summary, e_data_book_view_get_card_query (book_view));
567                 int i;
568
569                 for (i = 0; i < ids->len; i ++) {
570                         char *id = g_ptr_array_index (ids, i);
571
572                         if (!e_flag_is_set (closure->running))
573                                 break;
574
575                         string_to_dbt (id, &id_dbt);
576                         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
577                         vcard_dbt.flags = DB_DBT_MALLOC;
578
579                         db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
580
581                         if (db_error == 0) {
582                                 e_data_book_view_notify_update_prefiltered_vcard (book_view, id, vcard_dbt.data);
583                                 g_free (vcard_dbt.data);
584                         }
585                         else {
586                                 g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
587                         }
588                 }
589
590                 g_ptr_array_free (ids, TRUE);
591         }
592         else {
593                 /* iterate over the db and do the query there */
594                 DBC    *dbc;
595
596                 memset (&id_dbt, 0, sizeof (id_dbt));
597                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
598                 vcard_dbt.flags = DB_DBT_MALLOC;
599
600                 db_error = db->cursor (db, NULL, &dbc, 0);
601                 if (db_error == 0) {
602
603                         db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
604                         while (db_error == 0) {
605
606                                 if (!e_flag_is_set (closure->running))
607                                         break;
608
609                                 /* don't include the version in the list of cards */
610                                 if (strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
611                                         if (allcontacts)
612                                                 e_data_book_view_notify_update_prefiltered_vcard (book_view, id_dbt.data, vcard_dbt.data);
613                                         else
614                                                 e_data_book_view_notify_update_vcard (book_view, vcard_dbt.data);
615                                         g_free (vcard_dbt.data);
616                                 } else {
617                                         g_free (vcard_dbt.data);
618                                 }
619
620                                 db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
621                         }
622
623                         dbc->c_close (dbc);
624                         if (db_error && db_error != DB_NOTFOUND)
625                                 g_warning ("e_book_backend_file_search: error building list: %s",
626                                            db_strerror (db_error));
627                 }
628                 else if (db_error == DB_RUNRECOVERY) {
629                         g_warning ("e_book_backend_file_search: error getting the cursor for %s",
630                                    bf->priv->filename);
631                         abort ();
632                 }
633
634
635         }
636
637         if (e_flag_is_set (closure->running))
638                 e_data_book_view_notify_complete (book_view, GNOME_Evolution_Addressbook_Success);
639
640         /* unref the */
641         printf("book_view file uref \n");
642         bonobo_object_unref (book_view);
643
644         d(printf ("finished population of book view\n"));
645
646         return NULL;
647 }
648
649 static void
650 e_book_backend_file_start_book_view (EBookBackend  *backend,
651                                      EDataBookView *book_view)
652 {
653         FileBackendSearchClosure *closure = init_closure (book_view, E_BOOK_BACKEND_FILE (backend));
654
655         d(printf ("starting book view thread\n"));
656         closure->thread = g_thread_create (book_view_thread, book_view, TRUE, NULL);
657
658         e_flag_wait (closure->running);
659
660         /* at this point we know the book view thread is actually running */
661         d(printf ("returning from start_book_view\n"));
662 }
663
664 static void
665 e_book_backend_file_stop_book_view (EBookBackend  *backend,
666                                     EDataBookView *book_view)
667 {
668         FileBackendSearchClosure *closure = get_closure (book_view);
669         gboolean need_join;
670
671         d(printf ("stopping query\n"));
672         need_join = e_flag_is_set (closure->running);
673         e_flag_clear (closure->running);
674
675         if (need_join)
676                 g_thread_join (closure->thread);
677 }
678
679 typedef struct {
680         DB *db;
681
682         GList *add_cards;
683         GList *add_ids;
684         GList *mod_cards;
685         GList *mod_ids;
686         GList *del_ids;
687         GList *del_cards;
688 } EBookBackendFileChangeContext;
689
690 static void
691 e_book_backend_file_changes_foreach_key (const char *key, gpointer user_data)
692 {
693         EBookBackendFileChangeContext *ctx = user_data;
694         DB      *db = ctx->db;
695         DBT     id_dbt, vcard_dbt;
696         int     db_error = 0;
697         
698         string_to_dbt (key, &id_dbt);
699         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
700         vcard_dbt.flags = DB_DBT_MALLOC;
701
702         db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
703         
704         if (db_error != 0) {
705                 EContact *contact;
706                 char *id = id_dbt.data;
707                 char *vcard_string;
708
709                 contact = e_contact_new ();
710                 e_contact_set (contact, E_CONTACT_UID, id);
711                 
712                 vcard_string = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
713
714                 ctx->del_ids = g_list_append (ctx->del_ids,
715                                               g_strdup (id));
716                 ctx->del_cards = g_list_append (ctx->del_cards,
717                                                 vcard_string);
718
719                 g_object_unref (contact);
720                 
721                 g_free (vcard_dbt.data);
722         }
723 }
724
725 static EBookBackendSyncStatus
726 e_book_backend_file_get_changes (EBookBackendSync *backend,
727                                  EDataBook *book,
728                                  guint32 opid,
729                                  const char *change_id,
730                                  GList **changes_out)
731 {
732         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
733         int     db_error = 0;
734         DBT     id_dbt, vcard_dbt;
735         char    *filename;
736         EDbHash *ehash;
737         GList *i, *v;
738         DB      *db = bf->priv->file_db;
739         DBC *dbc;
740         GList *changes = NULL;
741         EBookBackendFileChangeContext ctx;
742         EBookBackendSyncStatus result;
743
744         memset (&id_dbt, 0, sizeof (id_dbt));
745         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
746
747         memset (&ctx, 0, sizeof (ctx));
748
749         ctx.db = db;
750
751         /* Find the changed ids */
752         filename = g_strdup_printf ("%s/%s" CHANGES_DB_SUFFIX, bf->priv->dirname, change_id);
753         ehash = e_dbhash_new (filename);
754         g_free (filename);
755
756         db_error = db->cursor (db, NULL, &dbc, 0);
757
758         if (db_error != 0) {
759                 g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
760         } else {
761                 db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
762
763                 while (db_error == 0) {
764
765                         /* don't include the version in the list of cards */
766                         if (id_dbt.size != strlen(E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
767                             || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
768                                 EContact *contact;
769                                 char *id = id_dbt.data;
770                                 char *vcard_string;
771                                 
772                                 /* Remove fields the user can't change
773                                  * and can change without the rest of the
774                                  * card changing 
775                                  */
776                                 contact = create_contact (id_dbt.data, vcard_dbt.data);
777
778 #ifdef notyet
779                                 g_object_set (card, "last_use", NULL, "use_score", 0.0, NULL);
780 #endif
781                                 vcard_string = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
782                                 g_object_unref (contact);
783                                 
784                                 /* check what type of change has occurred, if any */
785                                 switch (e_dbhash_compare (ehash, id, vcard_string)) {
786                                 case E_DBHASH_STATUS_SAME:
787                                         g_free(vcard_string);
788                                         break;
789                                 case E_DBHASH_STATUS_NOT_FOUND:
790                                         ctx.add_cards = g_list_append (ctx.add_cards, vcard_string);
791                                         ctx.add_ids = g_list_append (ctx.add_ids, g_strdup(id));
792                                         break;
793                                 case E_DBHASH_STATUS_DIFFERENT:
794                                         ctx.mod_cards = g_list_append (ctx.mod_cards, vcard_string);
795                                         ctx.mod_ids = g_list_append (ctx.mod_ids, g_strdup(id));
796                                         break;
797                                 }
798                         }
799
800                         db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
801                 }
802                 dbc->c_close (dbc);
803         }
804
805         e_dbhash_foreach_key (ehash, (EDbHashFunc)e_book_backend_file_changes_foreach_key, &ctx);
806
807         /* Send the changes */
808         if (db_error != DB_NOTFOUND) {
809                 g_warning ("e_book_backend_file_changes: error building list\n");
810                 *changes_out = NULL;
811                 result = db_error_to_status (db_error);
812         }
813         else {
814                 /* Update the hash and build our changes list */
815                 for (i = ctx.add_ids, v = ctx.add_cards; i != NULL; i = i->next, v = v->next){
816                         char *id = i->data;
817                         char *vcard = v->data;
818
819                         e_dbhash_add (ehash, id, vcard);
820                         changes = g_list_prepend (changes,
821                                                   e_book_backend_change_add_new (vcard));
822
823                         g_free (i->data);
824                         g_free (v->data);
825                 }       
826                 for (i = ctx.mod_ids, v = ctx.mod_cards; i != NULL; i = i->next, v = v->next){
827                         char *id = i->data;
828                         char *vcard = v->data;
829
830                         e_dbhash_add (ehash, id, vcard);
831                         changes = g_list_prepend (changes,
832                                                   e_book_backend_change_modify_new (vcard));
833
834                         g_free (i->data);
835                         g_free (v->data);               
836                 }
837                 for (i = ctx.del_ids, v = ctx.del_cards; i != NULL; i = i->next, v = v->next){  
838                         char *id = i->data;
839                         char *vcard = v->data;
840                         
841                         e_dbhash_remove (ehash, id);
842                         
843                         changes = g_list_prepend (changes,
844                                                   e_book_backend_change_delete_new (vcard));
845                         g_free (i->data);
846                         g_free (v->data);
847                 }
848
849                 e_dbhash_write (ehash);
850
851                 result = GNOME_Evolution_Addressbook_Success;
852                 *changes_out = changes;
853         }
854
855         e_dbhash_destroy (ehash);
856
857         return GNOME_Evolution_Addressbook_Success;
858 }
859
860 static char *
861 e_book_backend_file_extract_path_from_uri (const char *uri)
862 {
863         g_assert (g_ascii_strncasecmp (uri, "file://", 7) == 0);
864
865         return g_filename_from_uri (uri, NULL, NULL);
866 }
867
868 static EBookBackendSyncStatus
869 e_book_backend_file_authenticate_user (EBookBackendSync *backend,
870                                        EDataBook *book,
871                                        guint32 opid,
872                                        const char *user,
873                                        const char *passwd,
874                                        const char *auth_method)
875 {
876         return GNOME_Evolution_Addressbook_Success;
877 }
878
879 static EBookBackendSyncStatus
880 e_book_backend_file_get_required_fields (EBookBackendSync *backend,
881                                           EDataBook *book,
882                                           guint32 opid,
883                                           GList **fields_out)
884 {
885         GList *fields = NULL;
886         
887         fields = g_list_append (fields , g_strdup(e_contact_field_name (E_CONTACT_FILE_AS)));
888         *fields_out = fields;
889         return GNOME_Evolution_Addressbook_Success;
890 }
891
892
893 static EBookBackendSyncStatus
894 e_book_backend_file_get_supported_fields (EBookBackendSync *backend,
895                                           EDataBook *book,
896                                           guint32 opid,
897                                           GList **fields_out)
898 {
899         GList *fields = NULL;
900         int i;
901
902         /* XXX we need a way to say "we support everything", since the
903            file backend does */
904         for (i = 1; i < E_CONTACT_FIELD_LAST; i ++)
905                 fields = g_list_append (fields, g_strdup (e_contact_field_name (i)));
906
907         *fields_out = fields;
908         return GNOME_Evolution_Addressbook_Success;
909 }
910
911 /*
912 ** versions:
913 **
914 ** 0.0 just a list of cards
915 **
916 ** 0.1 same as 0.0, but with the version tag
917 **
918 ** 0.2 not a real format upgrade, just a hack to fix broken ids caused
919 **     by a bug in early betas, but we only need to convert them if
920 **     the previous version is 0.1, since the bug existed after 0.1
921 **     came about.
922 */
923 static gboolean
924 e_book_backend_file_upgrade_db (EBookBackendFile *bf, char *old_version)
925 {
926         DB  *db = bf->priv->file_db;
927         int db_error;
928         DBT version_name_dbt, version_dbt;
929         
930         if (strcmp (old_version, "0.0")
931             && strcmp (old_version, "0.1")) {
932                 g_warning ("unsupported version '%s' found in PAS backend file\n",
933                            old_version);
934                 return FALSE;
935         }
936
937         if (!strcmp (old_version, "0.1")) {
938                 /* we just loop through all the cards in the db,
939                    giving them valid ids if they don't have them */
940                 DBT  id_dbt, vcard_dbt;
941                 DBC *dbc;
942                 int  card_failed = 0;
943
944                 db_error = db->cursor (db, NULL, &dbc, 0);
945                 if (db_error != 0) {
946                         g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
947                         return FALSE;
948                 }
949
950                 memset (&id_dbt, 0, sizeof (id_dbt));
951                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
952
953                 db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_FIRST);
954
955                 while (db_error == 0) {
956                         if (id_dbt.size != strlen(E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
957                             || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
958                                 EContact *contact;
959
960                                 contact = create_contact (id_dbt.data, vcard_dbt.data);
961
962                                 /* the cards we're looking for are
963                                    created with a normal id dbt, but
964                                    with the id field in the vcard set
965                                    to something that doesn't match.
966                                    so, we need to modify the card to
967                                    have the same id as the the dbt. */
968                                 if (strcmp (id_dbt.data, e_contact_get_const (contact, E_CONTACT_UID))) {
969                                         char *vcard;
970
971                                         e_contact_set (contact, E_CONTACT_UID, id_dbt.data);
972
973                                         vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
974                                         string_to_dbt (vcard, &vcard_dbt);
975
976                                         db_error = db->put (db, NULL,
977                                                             &id_dbt, &vcard_dbt, 0);
978
979                                         g_free (vcard);
980
981                                         if (db_error != 0)
982                                                 card_failed++;
983                                 }
984
985                                 g_object_unref (contact);
986                         }
987
988                         db_error = dbc->c_get(dbc, &id_dbt, &vcard_dbt, DB_NEXT);
989                 }
990
991                 if (card_failed) {
992                         g_warning ("failed to update %d cards", card_failed);
993                         return FALSE;
994                 }
995         }
996
997         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION_NAME, &version_name_dbt);
998         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION, &version_dbt);
999
1000         db_error = db->put (db, NULL, &version_name_dbt, &version_dbt, 0);
1001         if (db_error == 0)
1002                 return TRUE;
1003         else
1004                 return FALSE;
1005 }
1006
1007 static gboolean
1008 e_book_backend_file_maybe_upgrade_db (EBookBackendFile *bf)
1009 {
1010         DB   *db = bf->priv->file_db;
1011         DBT  version_name_dbt, version_dbt;
1012         int  db_error;
1013         char *version;
1014         gboolean ret_val = TRUE;
1015
1016         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION_NAME, &version_name_dbt);
1017         memset (&version_dbt, 0, sizeof (version_dbt));
1018         version_dbt.flags = DB_DBT_MALLOC;
1019
1020         db_error = db->get (db, NULL, &version_name_dbt, &version_dbt, 0);
1021         if (db_error == 0) {
1022                 /* success */
1023                 version = version_dbt.data;
1024         }
1025         else {
1026                 /* key was not in file */
1027                 version = g_strdup ("0.0");
1028         }
1029
1030         if (strcmp (version, E_BOOK_BACKEND_FILE_VERSION))
1031                 ret_val = e_book_backend_file_upgrade_db (bf, version);
1032
1033         g_free (version);
1034
1035         return ret_val;
1036 }
1037
1038 #ifdef CREATE_DEFAULT_VCARD
1039 # include <libedata-book/ximian-vcard.h>
1040 #endif
1041
1042 static void
1043 #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3
1044 file_errcall (const DB_ENV *env, const char *buf1, const char *buf2)
1045 #else
1046 file_errcall (const char *buf1, char *buf2)
1047 #endif
1048 {
1049         g_warning ("libdb error: %s", buf2);
1050 }
1051
1052
1053 static GNOME_Evolution_Addressbook_CallStatus
1054 e_book_backend_file_load_source (EBookBackend           *backend,
1055                                  ESource                *source,
1056                                  gboolean                only_if_exists)
1057 {
1058         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1059         char           *dirname, *filename;
1060         gboolean        writable = FALSE;
1061         int             db_error;
1062         DB *db;
1063         DB_ENV *env;
1064         time_t db_mtime;
1065         struct stat sb;
1066         gchar *uri;
1067
1068         uri = e_source_get_uri (source);
1069
1070         dirname = e_book_backend_file_extract_path_from_uri (uri);
1071         filename = g_build_filename (dirname, "addressbook.db", NULL);
1072         g_free (uri);
1073
1074         db_error = e_db3_utils_maybe_recover (filename);
1075         if (db_error != 0) {
1076                 g_warning ("db recovery failed with %s", db_strerror (db_error));
1077                 g_free (dirname);
1078                 g_free (filename);
1079                 return db_error_to_status (db_error);
1080         }
1081
1082         g_static_mutex_lock(&global_env_lock);
1083         if (global_env.ref_count > 0) {
1084                 env = global_env.env;
1085                 global_env.ref_count++;
1086         } else {
1087                 db_error = db_env_create (&env, 0);
1088                 if (db_error != 0) {
1089                         g_warning ("db_env_create failed with %s", db_strerror (db_error));
1090                         g_static_mutex_unlock(&global_env_lock);
1091                         g_free (dirname);
1092                         g_free (filename);
1093                         return db_error_to_status (db_error);
1094                 }
1095
1096                 env->set_errcall (env, file_errcall);
1097
1098                 /* Set the allocation routines to the non-aborting GLib functions */
1099                 env->set_alloc (env, (void *(*)(size_t))g_try_malloc, 
1100                                 (void *(*)(void *, size_t))g_try_realloc,
1101                                 g_free);
1102
1103                 db_error = (*env->open) (env, NULL, DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_THREAD, 0);
1104                 if (db_error != 0) {
1105                         env->close(env, 0);
1106                         g_warning ("db_env_open failed with %s", db_strerror (db_error));
1107                         g_static_mutex_unlock(&global_env_lock);
1108                         g_free (dirname);
1109                         g_free (filename);
1110                         return db_error_to_status (db_error);
1111                 }
1112
1113                 global_env.env = env;
1114                 global_env.ref_count = 1;
1115         }
1116         g_static_mutex_unlock(&global_env_lock);
1117
1118         bf->priv->env = env;
1119
1120         db_error = db_create (&db, env, 0);
1121         if (db_error != 0) {
1122                 g_warning ("db_create failed with %s", db_strerror (db_error));
1123                 g_free (dirname);
1124                 g_free (filename);
1125                 return db_error_to_status (db_error);
1126         }
1127
1128         db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD, 0666);
1129
1130         if (db_error == DB_OLD_VERSION) {
1131                 db_error = e_db3_utils_upgrade_format (filename);
1132
1133                 if (db_error != 0) {
1134                         g_warning ("db format upgrade failed with %s", db_strerror (db_error));
1135                         g_free (dirname);
1136                         g_free (filename);
1137                         return db_error_to_status (db_error);
1138                 }
1139
1140                 db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD, 0666);
1141         }
1142
1143         bf->priv->file_db = db;
1144
1145         if (db_error == 0) {
1146                 writable = TRUE;
1147         } else {
1148                 db->close (db, 0);
1149                 
1150                 db_error = db_create (&db, env, 0);
1151                 if (db_error != 0) {
1152                         g_warning ("db_create failed with %s", db_strerror (db_error));
1153                         g_free (dirname);
1154                         g_free (filename);
1155                         return GNOME_Evolution_Addressbook_OtherError;
1156                 }
1157                 db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_RDONLY | DB_THREAD, 0666);
1158
1159                 if (db_error != 0 && !only_if_exists) {
1160                         int rv;
1161
1162                         /* the database didn't exist, so we create the
1163                            directory then the .db */
1164                         db->close (db, 0);
1165                         rv = g_mkdir_with_parents (dirname, 0777);
1166                         if (rv == -1 && errno != EEXIST) {
1167                                 g_warning ("failed to make directory %s: %s", dirname, strerror (errno));
1168                                 g_free (dirname);
1169                                 g_free (filename);
1170                                 if (errno == EACCES || errno == EPERM)
1171                                         return GNOME_Evolution_Addressbook_PermissionDenied;
1172                                 else
1173                                         return GNOME_Evolution_Addressbook_OtherError;
1174                         }
1175
1176                         db_error = db_create (&db, env, 0);
1177                         if (db_error != 0) {
1178                                 g_warning ("db_create failed with %s", db_strerror (db_error));
1179                                 g_free (dirname);
1180                                 g_free (filename);
1181                                 return GNOME_Evolution_Addressbook_OtherError;
1182                         }
1183
1184                         db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666);
1185                         if (db_error != 0) {
1186                                 db->close (db, 0);
1187                                 g_warning ("db->open (... DB_CREATE ...) failed with %s", db_strerror (db_error));
1188                         }
1189                         else {
1190 #ifdef CREATE_DEFAULT_VCARD
1191                                 EContact *contact = NULL;
1192
1193                                 do_create(bf, XIMIAN_VCARD, &contact);
1194                                 /* XXX check errors here */
1195                                 if (contact)
1196                                         g_object_unref (contact);
1197 #endif
1198
1199                                 writable = TRUE;
1200                         }
1201                 }
1202         }
1203
1204         if (db_error != 0) {
1205                 bf->priv->file_db = NULL;
1206                 g_free (dirname);
1207                 g_free (filename);
1208                 return db_error_to_status (db_error);
1209         }
1210
1211         if (!e_book_backend_file_maybe_upgrade_db (bf)) {
1212                 db->close (db, 0);
1213                 bf->priv->file_db = NULL;
1214                 g_warning ("e_book_backend_file_maybe_upgrade_db failed");
1215                 g_free (dirname);
1216                 g_free (filename);
1217                 return GNOME_Evolution_Addressbook_OtherError;
1218         }
1219
1220         g_free (bf->priv->dirname);
1221         g_free (bf->priv->filename);
1222         bf->priv->dirname = dirname;
1223         bf->priv->filename = filename;
1224
1225         if (g_stat (bf->priv->filename, &sb) == -1) {
1226                 db->close (db, 0);
1227                 bf->priv->file_db = NULL;
1228                 g_warning ("stat(%s) failed", bf->priv->filename);
1229                 return GNOME_Evolution_Addressbook_OtherError;
1230         }
1231         db_mtime = sb.st_mtime;
1232
1233         g_free (bf->priv->summary_filename);
1234         bf->priv->summary_filename = g_strconcat (bf->priv->filename, ".summary", NULL);
1235         bf->priv->summary = e_book_backend_summary_new (bf->priv->summary_filename, SUMMARY_FLUSH_TIMEOUT);
1236
1237         if (e_book_backend_summary_is_up_to_date (bf->priv->summary, db_mtime) == FALSE
1238             || e_book_backend_summary_load (bf->priv->summary) == FALSE ) {
1239                 build_summary (bf->priv);
1240         }
1241
1242         e_book_backend_set_is_loaded (backend, TRUE);
1243         e_book_backend_set_is_writable (backend, writable);
1244         return GNOME_Evolution_Addressbook_Success;
1245 }
1246
1247 static gboolean
1248 select_changes (const char *name)
1249 {
1250         char *p;
1251
1252         if (strlen (name) < strlen (CHANGES_DB_SUFFIX))
1253                 return FALSE;
1254
1255         p = strstr (name, CHANGES_DB_SUFFIX);
1256         if (!p)
1257                 return FALSE;
1258
1259         if (strlen (p) != strlen (CHANGES_DB_SUFFIX))
1260                 return FALSE;
1261
1262         return TRUE;
1263 }
1264
1265 static EBookBackendSyncStatus
1266 e_book_backend_file_remove (EBookBackendSync *backend,
1267                             EDataBook        *book,
1268                             guint32           opid)
1269 {
1270         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1271         GDir *dir;
1272
1273         if (-1 == g_unlink (bf->priv->filename)) {
1274                 if (errno == EACCES || errno == EPERM)
1275                         return GNOME_Evolution_Addressbook_PermissionDenied;
1276                 else
1277                         return GNOME_Evolution_Addressbook_OtherError;
1278         }
1279
1280         /* unref the summary before we remove the file so it's not written out again */
1281         g_object_unref (bf->priv->summary);
1282         bf->priv->summary = NULL;
1283         if (-1 == g_unlink (bf->priv->summary_filename))
1284                 g_warning ("failed to remove summary file `%s`: %s", bf->priv->summary_filename, strerror (errno));
1285
1286         dir = g_dir_open (bf->priv->dirname, 0, NULL);
1287         if (dir) {
1288                 const char *name;
1289
1290                 while ((name = g_dir_read_name (dir))) {
1291                         if (select_changes (name)) {
1292                                 char *full_path = g_build_filename (bf->priv->dirname, name, NULL);
1293                                 if (-1 == g_unlink (full_path)) {
1294                                         g_warning ("failed to remove change db `%s': %s", full_path, strerror (errno));
1295                                 }
1296                                 g_free (full_path);
1297                         }
1298                 }
1299
1300                 g_dir_close (dir);
1301         }
1302
1303         if (-1 == g_rmdir (bf->priv->dirname))
1304                 g_warning ("failed to remove directory `%s`: %s", bf->priv->dirname, strerror (errno));
1305
1306         /* we may not have actually succeeded in removing the
1307            backend's files/dirs, but there's nothing we can do about
1308            it here..  the only time we should return failure is if we
1309            failed to remove the actual data.  a failure should mean
1310            that the addressbook is still valid */
1311         return GNOME_Evolution_Addressbook_Success;
1312 }
1313
1314 static char *
1315 e_book_backend_file_get_static_capabilities (EBookBackend *backend)
1316 {
1317         return g_strdup("local,do-initial-query,bulk-removes,contact-lists");
1318 }
1319
1320 static GNOME_Evolution_Addressbook_CallStatus
1321 e_book_backend_file_cancel_operation (EBookBackend *backend, EDataBook *book)
1322 {
1323         return GNOME_Evolution_Addressbook_CouldNotCancel;
1324 }
1325 static void 
1326 e_book_backend_file_set_mode (EBookBackend *backend,  GNOME_Evolution_Addressbook_BookMode mode)
1327 {
1328         if (e_book_backend_is_loaded (backend)) {
1329                 e_book_backend_notify_writable (backend, TRUE);
1330                 e_book_backend_notify_connection_status (backend, TRUE);
1331         }
1332 }
1333
1334 static void 
1335 e_book_backend_file_sync (EBookBackend *backend)
1336 {
1337         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1338         int db_error;
1339         
1340         g_return_if_fail (bf != NULL);
1341         
1342         if (bf->priv->file_db) {
1343                 db_error = bf->priv->file_db->sync (bf->priv->file_db, 0);
1344                 if (db_error != 0)
1345                         g_warning (G_STRLOC ": db->sync failed with %s", db_strerror (db_error));
1346         }
1347 }
1348
1349 static gboolean
1350 e_book_backend_file_construct (EBookBackendFile *backend)
1351 {
1352         g_assert (backend != NULL);
1353         g_assert (E_IS_BOOK_BACKEND_FILE (backend));
1354
1355         if (! e_book_backend_construct (E_BOOK_BACKEND (backend)))
1356                 return FALSE;
1357
1358         return TRUE;
1359 }
1360
1361 /**
1362  * e_book_backend_file_new:
1363  */
1364 EBookBackend *
1365 e_book_backend_file_new (void)
1366 {
1367         EBookBackendFile *backend;
1368
1369         backend = g_object_new (E_TYPE_BOOK_BACKEND_FILE, NULL);
1370
1371         if (! e_book_backend_file_construct (backend)) {
1372                 g_object_unref (backend);
1373
1374                 return NULL;
1375         }
1376
1377         return E_BOOK_BACKEND (backend);
1378 }
1379
1380 static void
1381 e_book_backend_file_dispose (GObject *object)
1382 {
1383         EBookBackendFile *bf;
1384
1385         bf = E_BOOK_BACKEND_FILE (object);
1386
1387         if (bf->priv->file_db) {
1388                 bf->priv->file_db->close (bf->priv->file_db, 0);
1389                 bf->priv->file_db = NULL;
1390         }
1391         
1392         g_static_mutex_lock(&global_env_lock);
1393         global_env.ref_count--;
1394         if (global_env.ref_count == 0) {
1395                 global_env.env->close (global_env.env, 0);
1396                 global_env.env = NULL;
1397         }
1398         g_static_mutex_unlock (&global_env_lock);
1399         
1400         if (bf->priv->summary) {
1401                 g_object_unref (bf->priv->summary);
1402                 bf->priv->summary = NULL;
1403         }
1404
1405         G_OBJECT_CLASS (e_book_backend_file_parent_class)->dispose (object);    
1406 }
1407
1408 static void
1409 e_book_backend_file_finalize (GObject *object)
1410 {
1411         EBookBackendFile *bf;
1412
1413         bf = E_BOOK_BACKEND_FILE (object);
1414
1415         g_free (bf->priv->filename);
1416         g_free (bf->priv->dirname);
1417         g_free (bf->priv->summary_filename);
1418         
1419         g_free (bf->priv);
1420
1421         G_OBJECT_CLASS (e_book_backend_file_parent_class)->finalize (object);
1422 }
1423
1424 #ifdef G_OS_WIN32
1425 /* Avoid compiler warning by providing a function with exactly the
1426  * prototype that db_env_set_func_open() wants for the open method.
1427  */
1428
1429 static int
1430 my_open (const char *name, int oflag, ...)
1431 {
1432         int mode = 0;
1433
1434         if (oflag & O_CREAT) {
1435                 va_list arg;
1436                 va_start (arg, oflag);
1437                 mode = va_arg (arg, int);
1438                 va_end (arg);
1439         }
1440
1441         return g_open (name, oflag, mode);
1442 }
1443
1444 int
1445 my_rename (const char *oldname, const char *newname)
1446 {
1447         return g_rename (oldname, newname);
1448 }
1449
1450 int
1451 my_exists (const char *name, int *isdirp)
1452 {
1453         if (!g_file_test (name, G_FILE_TEST_EXISTS))
1454                 return ENOENT;
1455         if (isdirp != NULL)
1456                 *isdirp = g_file_test (name, G_FILE_TEST_IS_DIR);
1457         return 0;
1458 }
1459
1460 int
1461 my_unlink (const char *name)
1462 {
1463         return g_unlink (name);
1464 }
1465
1466 #endif
1467
1468 static void
1469 e_book_backend_file_class_init (EBookBackendFileClass *klass)
1470 {
1471         GObjectClass    *object_class = G_OBJECT_CLASS (klass);
1472         EBookBackendSyncClass *sync_class;
1473         EBookBackendClass *backend_class;
1474
1475         e_book_backend_file_parent_class = g_type_class_peek_parent (klass);
1476
1477         sync_class = E_BOOK_BACKEND_SYNC_CLASS (klass);
1478         backend_class = E_BOOK_BACKEND_CLASS (klass);
1479
1480         /* Set the virtual methods. */
1481         backend_class->load_source             = e_book_backend_file_load_source;
1482         backend_class->get_static_capabilities = e_book_backend_file_get_static_capabilities;
1483         backend_class->start_book_view         = e_book_backend_file_start_book_view;
1484         backend_class->stop_book_view          = e_book_backend_file_stop_book_view;
1485         backend_class->cancel_operation        = e_book_backend_file_cancel_operation;
1486         backend_class->set_mode                = e_book_backend_file_set_mode;
1487         backend_class->sync                    = e_book_backend_file_sync;
1488         sync_class->remove_sync                = e_book_backend_file_remove;
1489         sync_class->create_contact_sync        = e_book_backend_file_create_contact;
1490         sync_class->remove_contacts_sync       = e_book_backend_file_remove_contacts;
1491         sync_class->modify_contact_sync        = e_book_backend_file_modify_contact;
1492         sync_class->get_contact_sync           = e_book_backend_file_get_contact;
1493         sync_class->get_contact_list_sync      = e_book_backend_file_get_contact_list;
1494         sync_class->get_changes_sync           = e_book_backend_file_get_changes;
1495         sync_class->authenticate_user_sync     = e_book_backend_file_authenticate_user;
1496         sync_class->get_supported_fields_sync  = e_book_backend_file_get_supported_fields;
1497         sync_class->get_required_fields_sync   = e_book_backend_file_get_required_fields;
1498         
1499
1500         object_class->dispose = e_book_backend_file_dispose;
1501         object_class->finalize = e_book_backend_file_finalize;
1502
1503 #ifdef G_OS_WIN32
1504         /* Use the gstdio wrappers to open, check, rename and unlink
1505          * files from libdb.
1506          */
1507         db_env_set_func_open (my_open);
1508         db_env_set_func_close (close);
1509         db_env_set_func_exists (my_exists);
1510         db_env_set_func_rename (my_rename);
1511         db_env_set_func_unlink (my_unlink);
1512 #endif
1513 }
1514
1515 static void
1516 e_book_backend_file_init (EBookBackendFile *backend)
1517 {
1518         EBookBackendFilePrivate *priv;
1519
1520         priv             = g_new0 (EBookBackendFilePrivate, 1);
1521
1522         backend->priv = priv;
1523 }
1524
1525 /**
1526  * e_book_backend_file_get_type:
1527  */
1528 GType
1529 e_book_backend_file_get_type (void)
1530 {
1531         static GType type = 0;
1532
1533         if (! type) {
1534                 GTypeInfo info = {
1535                         sizeof (EBookBackendFileClass),
1536                         NULL, /* base_class_init */
1537                         NULL, /* base_class_finalize */
1538                         (GClassInitFunc)  e_book_backend_file_class_init,
1539                         NULL, /* class_finalize */
1540                         NULL, /* class_data */
1541                         sizeof (EBookBackendFile),
1542                         0,    /* n_preallocs */
1543                         (GInstanceInitFunc) e_book_backend_file_init
1544                 };
1545
1546                 type = g_type_register_static (E_TYPE_BOOK_BACKEND_SYNC, "EBookBackendFile", &info, 0);
1547         }
1548
1549         return type;
1550 }