Whitespace cleanups.
[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) 1999-2008 Novell, Inc. (www.novell.com)
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/gstdio.h>
40 #include <glib/gi18n-lib.h>
41
42 #include "libebackend/e-dbhash.h"
43 #include "libebackend/e-db3-utils.h"
44
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-sqlitedb.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 E_BOOK_BACKEND_FILE_GET_PRIVATE(obj) \
58         (G_TYPE_INSTANCE_GET_PRIVATE \
59         ((obj), E_TYPE_BOOK_BACKEND_FILE, EBookBackendFilePrivate))
60
61 #define d(x)
62
63 #define CHANGES_DB_SUFFIX ".changes.db"
64
65 #define E_BOOK_BACKEND_FILE_VERSION_NAME "PAS-DB-VERSION"
66 #define E_BOOK_BACKEND_FILE_VERSION "0.2"
67
68 #define E_BOOK_BACKEND_FILE_REVISION_NAME "PAS-DB-REVISION"
69
70 #define PAS_ID_PREFIX "pas-id-"
71
72 #define SQLITEDB_EMAIL_ID    "addressbook@localbackend.com"
73 #define SQLITEDB_FOLDER_ID   "folder_id"
74 #define SQLITEDB_FOLDER_NAME "folder"
75
76 #define EDB_ERROR(_code) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, NULL)
77 #define EDB_ERROR_EX(_code, _msg) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, _msg)
78 #define EDB_NOT_OPENED_ERROR EDB_ERROR(NOT_OPENED)
79
80 G_DEFINE_TYPE (EBookBackendFile, e_book_backend_file, E_TYPE_BOOK_BACKEND_SYNC)
81
82 struct _EBookBackendFilePrivate {
83         gchar     *dirname;
84         gchar     *filename;
85         gchar     *photo_dirname;
86         gchar     *revision;
87         gint       rev_counter;
88         DB        *file_db;
89         DB_ENV    *env;
90
91         EBookBackendSqliteDB *sqlitedb;
92 };
93
94 typedef enum {
95         GET_PATH_DB_DIR,
96         GET_PATH_PHOTO_DIR
97 } GetPathType;
98
99 typedef enum {
100         STATUS_NORMAL = 0,
101         STATUS_MODIFIED,
102         STATUS_ERROR
103 } PhotoModifiedStatus;
104
105 G_LOCK_DEFINE_STATIC (db_environments);
106 static GHashTable *db_environments = NULL;
107 typedef struct {
108         gint ref_count;
109         DB_ENV *env;
110 } global_env;
111
112 static void
113 db_error_to_gerror (const gint db_error,
114                     GError **perror)
115 {
116         if (db_error && perror && *perror)
117                 g_clear_error (perror);
118
119         switch (db_error) {
120         case 0:
121                 return;
122         case DB_NOTFOUND:
123                 g_propagate_error (perror, EDB_ERROR (CONTACT_NOT_FOUND));
124                 return;
125         case EACCES:
126                 g_propagate_error (perror, EDB_ERROR (PERMISSION_DENIED));
127                 return;
128         default:
129                 g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "db error 0x%x (%s)", db_error, db_strerror (db_error) ? db_strerror (db_error) : _("Unknown error")));
130                 return;
131         }
132 }
133
134 static void
135 string_to_dbt (const gchar *str,
136                DBT *dbt)
137 {
138         memset (dbt, 0, sizeof (*dbt));
139         dbt->data = (gpointer) str;
140         dbt->size = strlen (str) + 1;
141         dbt->flags = DB_DBT_USERMEM;
142 }
143
144 static gboolean
145 remove_file (const gchar *filename,
146              GError **error)
147 {
148         if (-1 == g_unlink (filename)) {
149                 if (errno == EACCES || errno == EPERM) {
150                         g_propagate_error (error, EDB_ERROR (PERMISSION_DENIED));
151                 } else {
152                         g_propagate_error (error, e_data_book_create_error_fmt
153                                            (E_DATA_BOOK_STATUS_OTHER_ERROR,
154                                             _("Failed to remove file '%s': %s"),
155                                             filename, g_strerror (errno)));
156                 }
157                 return FALSE;
158         }
159
160         return TRUE;
161 }
162
163 static gboolean
164 create_directory (const gchar *dirname,
165                   GError **error)
166 {
167         gint rv;
168
169         rv = g_mkdir_with_parents (dirname, 0700);
170         if (rv == -1 && errno != EEXIST) {
171                 g_warning ("failed to make directory %s: %s", dirname, g_strerror (errno));
172                 if (errno == EACCES || errno == EPERM)
173                         g_propagate_error (error, EDB_ERROR (PERMISSION_DENIED));
174                 else
175                         g_propagate_error (error,
176                                            e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR,
177                                                                          _("Failed to make directory %s: %s"), 
178                                                                          dirname, g_strerror (errno)));
179                 return FALSE;
180         }
181         return TRUE;
182 }
183
184 static EContact *
185 create_contact (const gchar *uid,
186                 const gchar *vcard)
187 {
188         return e_contact_new_from_vcard_with_uid (vcard, uid);
189 }
190
191 static gchar *
192 load_vcard (EBookBackendFile *bf,
193             DB_TXN *txn,
194             const gchar *uid,
195             GError **error)
196 {
197         DB     *db = bf->priv->file_db;
198         DBT     id_dbt, vcard_dbt;
199         gchar  *vcard;
200         gint    db_error;
201
202         /* Get the old contact from the db and compare the photo fields */
203         string_to_dbt (uid, &id_dbt);
204         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
205         vcard_dbt.flags = DB_DBT_MALLOC;
206
207         db_error = db->get (db, txn, &id_dbt, &vcard_dbt, 0);
208
209         if (db_error == 0) {
210                 vcard = vcard_dbt.data;
211         } else {
212                 g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
213                 g_propagate_error (error, EDB_ERROR (CONTACT_NOT_FOUND));
214                 return NULL;
215         }
216
217         return vcard;
218 }
219
220 static EContact *
221 load_contact (EBookBackendFile *bf,
222               DB_TXN *txn,
223               const gchar *uid,
224               GError **error)
225 {
226         EContact *contact = NULL;
227         gchar    *vcard;
228
229         if ((vcard = load_vcard (bf, txn, uid, error)) != NULL) {
230                 contact = create_contact (uid, vcard);
231                 g_free (vcard);
232         }
233
234         return contact;
235 }
236
237 static gchar *
238 check_remove_uri_for_field (EContact *old_contact,
239                             EContact *new_contact,
240                             EContactField field)
241 {
242         EContactPhoto *old_photo = NULL, *new_photo = NULL;
243         gchar         *uri = NULL;
244
245         old_photo = e_contact_get (old_contact, field);
246         if (!old_photo)
247                 return NULL;
248
249         if (new_contact) {
250
251                 new_photo = e_contact_get (new_contact, field);
252
253                 if (new_photo == NULL ||
254                     g_ascii_strcasecmp (old_photo->data.uri, new_photo->data.uri))
255                         uri = g_strdup (old_photo->data.uri);
256         } else {
257                 uri = g_strdup (old_photo->data.uri);
258         }
259
260         e_contact_photo_free (old_photo);
261         e_contact_photo_free (new_photo);
262
263         return uri;
264 }
265
266 static void
267 maybe_delete_uri (EBookBackendFile *bf,
268                   const gchar *uri)
269 {
270         GError *error = NULL;
271         gchar  *filename;
272
273         /* A uri that does not give us a filename is certainly not
274          * a uri that we created for a local file, just skip it */
275         if ((filename = g_filename_from_uri (uri, NULL, NULL)) == NULL)
276                 return;
277
278         /* If the file is in our path it belongs to us and we need to delete it.
279          */
280         if (!strncmp (bf->priv->photo_dirname, filename, strlen (bf->priv->photo_dirname))) {
281
282                 d(g_print ("Deleting uri file: %s\n", filename));
283
284                 /* Deleting uris should not cause the backend to fail to update
285                  * a contact so the best we can do from here is log warnings
286                  * when we fail to unlink a file from the disk.
287                  */
288                 if (!remove_file (filename, &error)) {
289                         g_warning ("Unable to cleanup photo uri: %s", error->message);
290                         g_error_free (error);
291                 }
292         }
293
294         g_free (filename);
295 }
296
297 static void
298 maybe_delete_unused_uris (EBookBackendFile *bf,
299                           EContact *old_contact,
300                           EContact *new_contact)
301 {
302         gchar             *uri_photo, *uri_logo;
303
304         g_return_if_fail (old_contact != NULL);
305
306         /* If there is no new contact, collect all the uris to delete from old_contact
307          *
308          * Otherwise, if any of the photo uri fields have changed in new_contact, then collect the
309          * old uris for those fields from old_contact to delete
310          */
311         uri_photo = check_remove_uri_for_field (old_contact, new_contact, E_CONTACT_PHOTO);
312         uri_logo  = check_remove_uri_for_field (old_contact, new_contact, E_CONTACT_LOGO);
313
314         if (uri_photo) {
315                 maybe_delete_uri (bf, uri_photo);
316                 g_free (uri_photo);
317         }
318
319         if (uri_logo) {
320                 maybe_delete_uri (bf, uri_logo);
321                 g_free (uri_logo);
322         }
323 }
324
325 static gchar *
326 e_book_backend_file_extract_path_from_source (ESource *source,
327                                               GetPathType path_type)
328 {
329         const gchar *user_data_dir;
330         const gchar *source_dir;
331         gchar *mangled_source_dir;
332         gchar *filename = NULL;
333
334         user_data_dir = e_get_user_data_dir ();
335         source_dir = e_source_peek_relative_uri (source);
336
337         if (!source_dir || !g_str_equal (source_dir, "system"))
338                 source_dir = e_source_get_uid (source);
339
340         /* Mangle the URI to not contain invalid characters. */
341         mangled_source_dir = g_strdelimit (g_strdup (source_dir), ":/", '_');
342
343         switch (path_type) {
344         case GET_PATH_DB_DIR:
345                 filename = g_build_filename
346                         (user_data_dir, "addressbook", mangled_source_dir, NULL);
347                 break;
348         case GET_PATH_PHOTO_DIR:
349                 filename = g_build_filename
350                         (user_data_dir, "addressbook", mangled_source_dir, "photos", NULL);
351                 break;
352         default:
353                 break;
354         }
355         g_free (mangled_source_dir);
356
357         return filename;
358 }
359
360 static gchar *
361 safe_name_for_photo (EBookBackendFile *bf,
362                      EContact *contact,
363                      EContactPhoto *photo,
364                      EContactField field)
365 {
366         gchar         *fullname = NULL, *name, *str;
367         gchar         *suffix = NULL;
368         gint           i = 0;
369
370         g_assert (photo->type == E_CONTACT_PHOTO_TYPE_INLINED);
371
372         /* Get a suitable filename extension */
373         if (photo->data.inlined.mime_type != NULL &&
374             photo->data.inlined.mime_type[0] != '\0') {
375                 suffix = g_uri_escape_string (photo->data.inlined.mime_type,
376                                               NULL, TRUE);
377         } else {
378                 gchar *mime_type = NULL;
379                 gchar *content_type = NULL;
380
381                 content_type = g_content_type_guess (NULL,
382                                                      photo->data.inlined.data,
383                                                      photo->data.inlined.length,
384                                                      NULL);
385
386                 if (content_type)
387                         mime_type = g_content_type_get_mime_type (content_type);
388
389                 if (mime_type)
390                         suffix = g_uri_escape_string (mime_type, NULL, TRUE);
391                 else
392                         suffix = g_strdup ("data");
393
394                 g_free (mime_type);
395                 g_free (content_type);
396         }
397
398         /* Create a filename based on the uid/field */
399         name = g_strconcat (e_contact_get_const (contact, E_CONTACT_UID), "_",
400                             e_contact_field_name (field), NULL);
401         name = g_strdelimit (name, NULL, '_');
402
403         do {
404                 g_free (fullname);
405
406                 str      = e_filename_mkdir_encoded (bf->priv->photo_dirname, name, NULL, i);
407                 fullname = g_strdup_printf ("%s.%s", str, suffix);
408                 g_free (str);
409
410                 i++;
411         } while (g_file_test (fullname, G_FILE_TEST_EXISTS));
412
413         g_free (name);
414         g_free (suffix);
415
416         return fullname;
417 }
418
419 static gchar *
420 hard_link_photo (EBookBackendFile *bf,
421                  EContact *contact,
422                  EContactField field,
423                  const gchar *src_filename,
424                  GError **error)
425 {
426         gchar *fullname = NULL, *name, *str;
427         gint   i = 0, ret;
428         const gchar *suffix;
429
430         /* Copy over the file suffix */
431         suffix = strrchr (src_filename, '.');
432         if (suffix)
433                 suffix++;
434
435         if (!suffix)
436                 suffix = "data";
437
438         /* Create a filename based on uid/field */
439         name = g_strconcat (e_contact_get_const (contact, E_CONTACT_UID), "_",
440                             e_contact_field_name (field), NULL);
441         name = g_strdelimit (name, NULL, '_');
442
443         do {
444                 g_free (fullname);
445
446                 str      = e_filename_mkdir_encoded (bf->priv->photo_dirname, name, NULL, i);
447                 fullname = g_strdup_printf ("%s.%s", str, suffix);
448                 g_free (str);
449
450                 i++;
451
452                 ret = link (src_filename, fullname);
453
454         } while (ret < 0 && errno == EEXIST);
455
456         if (ret < 0) {
457                 if (errno == EACCES || errno == EPERM) {
458                         g_propagate_error (error, EDB_ERROR (PERMISSION_DENIED));
459                 } else {
460                         g_propagate_error (error, e_data_book_create_error_fmt
461                                            (E_DATA_BOOK_STATUS_OTHER_ERROR,
462                                             _("Failed to create hardlink for resource '%s': %s"),
463                                             src_filename, g_strerror (errno)));
464                 }
465                 g_free (fullname);
466                 fullname = NULL;
467         }
468
469         g_free (name);
470
471         return fullname;
472 }
473
474 static gboolean
475 is_backend_owned_uri (EBookBackendFile *bf,
476                       const gchar *uri)
477 {
478         gchar     *filename;
479         gchar     *dirname;
480         gboolean   owned_uri;
481
482         /* Errors converting from uri definitily indicate it was
483          * not our uri to begin with, so just disregard this error. */
484         filename = g_filename_from_uri (uri, NULL, NULL);
485         if (!filename)
486                 return FALSE;
487
488         dirname = g_path_get_dirname (filename);
489
490         owned_uri = (strcmp (dirname, bf->priv->photo_dirname) == 0);
491
492         g_free (filename);
493         g_free (dirname);
494
495         return owned_uri;
496 }
497
498 static PhotoModifiedStatus
499 maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
500                                        EContact *old_contact,
501                                        EContact *contact,
502                                        EContactField field,
503                                        GError **error)
504 {
505         PhotoModifiedStatus  status = STATUS_NORMAL;
506         EContactPhoto       *photo;
507
508         if (field != E_CONTACT_PHOTO && field != E_CONTACT_LOGO)
509                 return status;
510
511         photo = e_contact_get (contact, field);
512         if (!photo)
513                 return status;
514
515         if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
516                 EContactPhoto *new_photo;
517                 gchar         *new_photo_path;
518                 gchar         *uri;
519
520                 /* Create a unique filename with an extension (hopefully) based on the mime type */
521                 new_photo_path = safe_name_for_photo (bf, contact, photo, field);
522
523                 if ((uri =
524                      g_filename_to_uri (new_photo_path, NULL, error)) == NULL) {
525
526                         status = STATUS_ERROR;
527                 } else if (!g_file_set_contents (new_photo_path,
528                                                  (const gchar *) photo->data.inlined.data,
529                                                  photo->data.inlined.length,
530                                                  error)) {
531
532                         status = STATUS_ERROR;
533                 } else {
534                         new_photo           = e_contact_photo_new ();
535                         new_photo->type     = E_CONTACT_PHOTO_TYPE_URI;
536                         new_photo->data.uri = g_strdup (uri);
537
538                         e_contact_set (contact, field, new_photo);
539
540                         d(g_print ("Backend modified incomming binary blob to be %s:\n", uri));
541
542                         status = STATUS_MODIFIED;
543
544                         e_contact_photo_free (new_photo);
545                 }
546
547                 g_free (uri);
548                 g_free (new_photo_path);
549
550         } else { /* E_CONTACT_PHOTO_TYPE_URI */
551                 const gchar       *uid;
552                 EContactPhoto     *old_photo = NULL, *new_photo;
553
554                 /* First determine that the new contact uri points to our 'photos' directory,
555                  * if not then we do nothing
556                  */
557                 if (!is_backend_owned_uri (bf, photo->data.uri))
558                         goto done;
559
560                 /* Now check if the uri is changed from the BDB copy
561                  */
562                 uid = e_contact_get_const (contact, E_CONTACT_UID);
563                 if (uid == NULL) {
564                         g_propagate_error (error, EDB_ERROR_EX (OTHER_ERROR, _("No UID in the contact")));
565                         status = STATUS_ERROR;
566                         goto done;
567                 }
568
569                 if (old_contact)
570                         old_photo = e_contact_get (old_contact, field);
571
572                 /* Unless we are receiving the same uri that we already have
573                  * stored in the BDB... */
574                 if (!old_photo || old_photo->type == E_CONTACT_PHOTO_TYPE_INLINED ||
575                     g_ascii_strcasecmp (old_photo->data.uri, photo->data.uri) != 0) {
576                         gchar *filename;
577                         gchar *new_filename;
578                         gchar *new_uri = NULL;
579
580                         /* ... Assume that the incomming uri belongs to another contact
581                          * still in the BDB. Lets go ahead and create a hard link to the 
582                          * photo file and create a new name for the incomming uri, and
583                          * use that in the incomming contact to save in place.
584                          *
585                          * This piece of code is here to ensure there are no problems if
586                          * the libebook user decides to cross-reference and start "sharing"
587                          * uris that we've previously stored in the photo directory.
588                          *
589                          * We use the hard-link here to off-load the necessary ref-counting
590                          * logic to the file-system.
591                          */
592                         filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
593                         g_assert (filename); /* we already checked this with 'is_backend_owned_uri ()' */
594
595                         new_filename = hard_link_photo (bf, contact, field, filename, error);
596
597                         if (!new_filename)
598                                 status = STATUS_ERROR;
599                         else if ((new_uri = g_filename_to_uri (new_filename, NULL, error)) == NULL) {
600                                 /* If we fail here... we need to clean up the hardlink we just created */
601                                 GError *local_err = NULL;
602                                 if (!remove_file (new_filename, &local_err)) {
603                                         g_warning ("Unable to cleanup photo uri: %s", local_err->message);
604                                         g_error_free (local_err);
605                                 }
606                                 status = STATUS_ERROR;
607                         } else {
608
609                                 new_photo           = e_contact_photo_new ();
610                                 new_photo->type     = E_CONTACT_PHOTO_TYPE_URI;
611                                 new_photo->data.uri = new_uri;
612
613                                 e_contact_set (contact, field, new_photo);
614
615                                 d(g_print ("Backend modified incomming shared uri to be %s:\n", new_uri));
616
617                                 e_contact_photo_free (new_photo);
618                                 status = STATUS_MODIFIED;
619                         }
620                         g_free (new_filename);
621                         g_free (filename);
622                 }
623
624                 if (old_photo)
625                         e_contact_photo_free (old_photo);
626
627         }
628
629  done:
630         e_contact_photo_free (photo);
631
632         return status;
633 }
634
635 /*
636  * When a contact is added or modified we receive a vCard,
637  * this function checks if we've received inline data
638  * and replaces it with a uri notation.
639  *
640  * If this function modifies 'contact' then it will
641  * return the 'modified' status and 'vcard_ret' (if specified)
642  * will be set to a newly allocated vcard string.
643  */
644 static PhotoModifiedStatus
645 maybe_transform_vcard_for_photo (EBookBackendFile *bf,
646                                  EContact *old_contact,
647                                  EContact *contact,
648                                  gchar **vcard_ret,
649                                  GError **error)
650 {
651         PhotoModifiedStatus status;
652         gboolean            modified = FALSE;
653
654         status   = maybe_transform_vcard_field_for_photo (bf, old_contact, contact,
655                                                           E_CONTACT_PHOTO, error);
656         modified = (status == STATUS_MODIFIED);
657
658         if (status != STATUS_ERROR) {
659                 status   = maybe_transform_vcard_field_for_photo (bf, old_contact, contact,
660                                                                   E_CONTACT_LOGO, error);
661                 modified = modified || (status == STATUS_MODIFIED);
662         }
663
664         if (status != STATUS_ERROR) {
665                 if (modified) {
666                         if (vcard_ret)
667                                 *vcard_ret = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
668                         status = STATUS_MODIFIED;
669                 }
670         }
671
672         return status;
673 }
674
675 static gboolean
676 build_sqlitedb (EBookBackendFilePrivate *bfpriv)
677 {
678         DB             *db = bfpriv->file_db;
679         DBC            *dbc;
680         gint            db_error;
681         DBT             id_dbt, vcard_dbt;
682         GSList         *contacts = NULL;
683         GError         *error = NULL;
684         gboolean        skipped_version = FALSE;
685         gboolean        skipped_revision = FALSE;
686
687         if (!db) {
688                 g_warning (G_STRLOC ": Not opened yet");
689                 return FALSE;
690         }
691
692         db_error = db->cursor (db, NULL, &dbc, 0);
693
694         if (db_error != 0) {
695                 g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
696                 return FALSE;
697         }
698
699         memset (&vcard_dbt, 0, sizeof (vcard_dbt));
700         memset (&id_dbt, 0, sizeof (id_dbt));
701         db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_FIRST);
702
703         while (db_error == 0) {
704                 gboolean skip = FALSE;
705
706                 /* don't include the version and revision in the list of cards */
707                 if (!skipped_version && !strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) {
708                         skipped_version = TRUE;
709                         skip = TRUE;
710                 } else if (!skipped_revision && !strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_REVISION_NAME)) {
711                         skipped_revision = TRUE;
712                         skip = TRUE;
713                 }
714
715                 if (!skip) {
716                         EContact *contact = create_contact (id_dbt.data, vcard_dbt.data);
717
718                         contacts = g_slist_prepend (contacts, contact);
719                 }
720
721                 db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_NEXT);
722
723         }
724
725         dbc->c_close (dbc);
726
727         /* Detect error case */
728         if (db_error != DB_NOTFOUND) {
729                 g_warning (G_STRLOC ": dbc->c_get failed with %s", db_strerror (db_error));
730                 e_util_free_object_slist (contacts);
731                 return FALSE;
732         }
733
734         if (!e_book_backend_sqlitedb_add_contacts (bfpriv->sqlitedb,
735                                                    SQLITEDB_FOLDER_ID,
736                                                    contacts, FALSE, &error)) {
737                 g_warning ("Failed to build contact summary: %s", error->message);
738                 g_error_free (error);
739                 e_util_free_object_slist (contacts);
740                 return FALSE;
741         }
742
743         e_util_free_object_slist (contacts);
744
745         if (!e_book_backend_sqlitedb_set_is_populated (bfpriv->sqlitedb, SQLITEDB_FOLDER_ID, TRUE, &error)) {
746                 g_warning ("Failed to set the sqlitedb populated flag: %s", error->message);
747                 g_error_free (error);
748                 return FALSE;
749         }
750
751         return TRUE;
752 }
753
754 static gchar *
755 e_book_backend_file_create_unique_id (void)
756 {
757         /* use a 32 counter and the 32 bit timestamp to make an id.
758          * it's doubtful 2^32 id's will be created in a second, so we
759          * should be okay. */
760         static guint c = 0;
761         return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", time(NULL), c++);
762 }
763
764 static gchar *
765 e_book_backend_file_new_revision (EBookBackendFile *bf)
766 {
767         gchar time_string[100] = {0};
768         const struct tm *tm = NULL;
769         time_t t;
770
771         t = time (NULL);
772         tm = gmtime (&t);
773         if (tm)
774                 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", tm);
775
776         return g_strdup_printf ("%s(%d)", time_string, bf->priv->rev_counter++);
777 }
778
779 /* For now just bump the revision and set it in the DB every
780  * time the revision bumps, this is the safest approach and
781  * its unclear so far if bumping the revision string for 
782  * every DB modification is going to really be an overhead.
783  */
784 static void
785 e_book_backend_file_bump_revision (EBookBackendFile *bf)
786 {
787         DB   *db = bf->priv->file_db;
788         DBT  revision_name_dbt, revision_dbt;
789         gint  db_error;
790
791         g_free (bf->priv->revision);
792         bf->priv->revision = e_book_backend_file_new_revision (bf);
793
794         string_to_dbt (E_BOOK_BACKEND_FILE_REVISION_NAME, &revision_name_dbt);
795         string_to_dbt (bf->priv->revision,                &revision_dbt);
796         db_error = db->put (db, NULL, &revision_name_dbt, &revision_dbt, 0);
797
798         if (db_error != 0)
799                 g_warning (G_STRLOC ": db->put failed while bumping the revision string: %s",
800                            db_strerror (db_error));
801
802         e_book_backend_notify_property_changed (E_BOOK_BACKEND (bf),
803                                                 BOOK_BACKEND_PROPERTY_REVISION,
804                                                 bf->priv->revision);
805 }
806
807 static void
808 e_book_backend_file_load_revision (EBookBackendFile *bf)
809 {
810         DB   *db = bf->priv->file_db;
811         DBT  version_name_dbt, version_dbt;
812         gint  db_error;
813
814         string_to_dbt (E_BOOK_BACKEND_FILE_REVISION_NAME, &version_name_dbt);
815         memset (&version_dbt, 0, sizeof (version_dbt));
816         version_dbt.flags = DB_DBT_MALLOC;
817
818         db_error = db->get (db, NULL, &version_name_dbt, &version_dbt, 0);
819         if (db_error == 0) {
820                 /* success */
821                 bf->priv->revision = version_dbt.data;
822         }
823         else {
824                 /* key was not in file */
825                 bf->priv->revision = e_book_backend_file_new_revision (bf);
826         }
827 }
828
829 static void
830 set_revision (EContact *contact)
831 {
832         gchar time_string[100] = {0};
833         const struct tm *tm = NULL;
834         time_t t;
835
836         t = time (NULL);
837         tm = gmtime (&t);
838         if (tm)
839                 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", tm);
840         e_contact_set (contact, E_CONTACT_REV, time_string);
841
842 }
843
844 /**
845  * This method will return TRUE if all the contacts were properly created.
846  * If at least one contact fails, the method will return FALSE, all
847  * changes will be reverted (the @contacts list will stay empty) and
848  * @perror will be set.
849  */
850 static gboolean
851 do_create (EBookBackendFile *bf,
852           const GSList *vcards_req,
853           GSList **contacts,
854           GError **perror)
855 {
856         DB *db = bf->priv->file_db;
857         DB_ENV *env = bf->priv->env;
858         DB_TXN *txn = NULL;
859         GSList *slist = NULL;
860         const GSList *l;
861         gint db_error = 0;
862         PhotoModifiedStatus status = STATUS_NORMAL;
863
864         g_assert (bf);
865         g_assert (vcards_req);
866
867         if (!db) {
868                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
869                 return FALSE;
870         }
871
872         /* Begin transaction */
873         db_error = env->txn_begin (env, NULL, &txn, 0);
874         if (db_error != 0) {
875                 g_warning (G_STRLOC ": env->txn_begin failed with %s", db_strerror (db_error));
876                 db_error_to_gerror (db_error, perror);
877                 return FALSE;
878         }
879
880         for (l = vcards_req; l != NULL; l = l->next) {
881                 DBT              id_dbt, vcard_dbt;
882                 gchar           *id;
883                 gchar           *vcard;
884                 const gchar     *rev;
885                 const gchar     *vcard_req;
886                 EContact        *contact;
887
888                 vcard_req = (const gchar *) l->data;
889
890                 id      = e_book_backend_file_create_unique_id ();
891                 contact = e_contact_new_from_vcard_with_uid (vcard_req, id);
892
893                 rev = e_contact_get_const (contact, E_CONTACT_REV);
894                 if (!(rev && *rev))
895                         set_revision (contact);
896
897                 status = maybe_transform_vcard_for_photo (bf, NULL, contact, NULL, perror);
898
899                 if (status != STATUS_ERROR) {
900                         vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
901
902                         string_to_dbt (id,    &id_dbt);
903                         string_to_dbt (vcard, &vcard_dbt);
904
905                         db_error = db->put (db, txn, &id_dbt, &vcard_dbt, 0);
906
907                         g_free (vcard);
908                 }
909
910                 g_free (id);
911
912                 if (db_error == 0 && status != STATUS_ERROR) {
913                         /* Contact was added successfully, add it to the return list */
914                         if (contacts != NULL)
915                                 slist = g_slist_prepend (slist, contact);
916                 } else if (db_error != 0) {
917                         /* Contact could not be added */
918                         g_warning (G_STRLOC ": db->put failed with %s", db_strerror (db_error));
919                         g_object_unref (contact);
920                         db_error_to_gerror (db_error, perror);
921
922                         /* Abort as soon as an error occurs */
923                         break;
924                 } else if (status == STATUS_ERROR) {
925                         /* Contact could not be added */
926                         g_warning (G_STRLOC ": db->put failed with %s",
927                                    (perror && *perror) ? (*perror)->message :
928                                    "Unknown error transforming vcard");
929                         g_object_unref (contact);
930
931                         /* Abort as soon as an error occurs */
932                         break;
933                 }
934         }
935
936         if (db_error == 0 && status != STATUS_ERROR) {
937                 /* Commit transaction */
938                 db_error = txn->commit (txn, 0);
939                 if (db_error == 0) {
940                         /* Flush cache information to disk */
941                         if (db->sync (db, 0) != 0) {
942                                 g_warning ("db->sync failed with %s", db_strerror (db_error));
943                         }
944                 } else {
945                         g_warning (G_STRLOC ": txn->commit failed with %s", db_strerror (db_error));
946                         db_error_to_gerror (db_error, perror);
947                 }
948
949         } else {
950                 /* Rollback transaction */
951                 txn->abort (txn);
952         }
953
954         if (db_error == 0 && status != STATUS_ERROR) {
955                 if (contacts != NULL)
956                         *contacts = g_slist_reverse (slist);
957
958                 return TRUE;
959         } else {
960                 if (contacts != NULL)
961                         *contacts = NULL;
962
963                 e_util_free_object_slist (slist);
964                 return FALSE;
965         }
966 }
967
968 static void
969 e_book_backend_file_create_contacts (EBookBackendSync *backend,
970                                      EDataBook *book,
971                                      GCancellable *cancellable,
972                                      const GSList *vcards,
973                                      GSList **added_contacts,
974                                      GError **perror)
975 {
976         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
977
978         if (do_create (bf, vcards, added_contacts, perror)) {
979                 GError *error = NULL;
980
981                 if (!e_book_backend_sqlitedb_add_contacts (bf->priv->sqlitedb,
982                                                           SQLITEDB_FOLDER_ID,
983                                                           *added_contacts, FALSE, &error)) {
984                         g_warning ("Failed to add contacts to summary: %s", error->message);
985                         g_error_free (error);
986                 }
987
988                 e_book_backend_file_bump_revision (bf);
989         }
990 }
991
992 static void
993 e_book_backend_file_remove_contacts (EBookBackendSync *backend,
994                                      EDataBook *book,
995                                      GCancellable *cancellable,
996                                      const GSList *id_list,
997                                      GSList **ids,
998                                      GError **perror)
999 {
1000         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1001         DB               *db = bf->priv->file_db;
1002         DB_ENV           *env = bf->priv->env;
1003         DB_TXN           *txn = NULL;
1004         gint              db_error;
1005         GSList           *removed_ids = NULL, *removed_contacts = NULL;
1006         const GSList     *l;
1007
1008         if (!db) {
1009                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
1010                 return;
1011         }
1012
1013         /* Begin transaction */
1014         db_error = env->txn_begin (env, NULL, &txn, 0);
1015         if (db_error != 0) {
1016                 g_warning (G_STRLOC ": env->txn_begin failed with %s", db_strerror (db_error));
1017                 db_error_to_gerror (db_error, perror);
1018                 return;
1019         }
1020
1021         for (l = id_list; l != NULL; l = l->next) {
1022                 const gchar *id;
1023                 EContact *contact;
1024                 DBT id_dbt;
1025
1026                 id = l->data;
1027
1028                 contact = load_contact (bf, txn, id, NULL);
1029                 if (contact)
1030                         removed_contacts = g_slist_prepend (removed_contacts, contact);
1031
1032                 /* Then go on to delete from the db */
1033                 string_to_dbt (id, &id_dbt);
1034
1035                 db_error = db->del (db, txn, &id_dbt, 0);
1036                 if (db_error != 0) {
1037                         g_warning (G_STRLOC ": db->del failed with %s", db_strerror (db_error));
1038                         db_error_to_gerror (db_error, perror);
1039                         /* Abort as soon as a removal fails */
1040                         break;
1041                 }
1042
1043                 removed_ids = g_slist_prepend (removed_ids, g_strdup (id));
1044         }
1045
1046         if (db_error == 0) {
1047                 /* Commit transaction */
1048                 db_error = txn->commit (txn, 0);
1049                 if (db_error == 0) {
1050                         /* Flush cache information to disk */
1051                         if (db->sync (db, 0) != 0) {
1052                                 g_warning ("db->sync failed with %s", db_strerror (db_error));
1053                         }
1054                 } else {
1055                         g_warning (G_STRLOC ": txn->commit failed with %s", db_strerror (db_error));
1056                         db_error_to_gerror (db_error, perror);
1057                 }
1058         } else {
1059                 /* Rollback transaction */
1060                 txn->abort (txn);
1061         }
1062
1063         if (db_error == 0) {
1064                 GError *error = NULL;
1065
1066                 /* Delete URI associated to those contacts */
1067                 for (l = removed_contacts; l; l = l->next) {
1068                         maybe_delete_unused_uris (bf, E_CONTACT (l->data), NULL);
1069                 }
1070
1071                 /* Remove from summary as well */
1072                 if (!e_book_backend_sqlitedb_remove_contacts (bf->priv->sqlitedb,
1073                                                       SQLITEDB_FOLDER_ID,
1074                                                       removed_ids, &error)) {
1075                         g_warning ("Failed to remove contacts from the summary: %s", error->message);
1076                         g_error_free (error);
1077                 }
1078
1079                 *ids = removed_ids;
1080         } else {
1081                 *ids = NULL;
1082                 e_util_free_string_slist (removed_ids);
1083         }
1084
1085         e_book_backend_file_bump_revision (bf);
1086         g_slist_free_full (removed_contacts, g_object_unref);
1087 }
1088
1089 static void
1090 e_book_backend_file_modify_contacts (EBookBackendSync *backend,
1091                                      EDataBook *book,
1092                                      GCancellable *cancellable,
1093                                      const GSList *vcards,
1094                                      GSList **contacts,
1095                                      GError **perror)
1096 {
1097         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1098         DB               *db = bf->priv->file_db;
1099         DB_ENV           *env = bf->priv->env;
1100         DB_TXN           *txn = NULL;
1101         gint              db_error;
1102         const GSList     *lold, *l;
1103         GSList           *old_contacts = NULL, *modified_contacts = NULL;
1104         GSList           *ids = NULL;
1105         PhotoModifiedStatus status = STATUS_NORMAL;
1106
1107         if (!db) {
1108                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
1109                 return;
1110         }
1111
1112         /* Begin transaction */
1113         db_error = env->txn_begin (env, NULL, &txn, 0);
1114         if (db_error != 0) {
1115                 g_warning (G_STRLOC ": env->txn_begin failed with %s", db_strerror (db_error));
1116                 db_error_to_gerror (db_error, perror);
1117                 return;
1118         }
1119
1120         for (l = vcards; l != NULL; l = l->next) {
1121                 gchar *id, *lookup_id;
1122                 gchar *vcard_with_rev;
1123                 DBT id_dbt, vcard_dbt;
1124                 EContact *contact, *old_contact;
1125
1126                 contact = e_contact_new_from_vcard (l->data);
1127                 id = e_contact_get (contact, E_CONTACT_UID);
1128
1129                 if (id == NULL) {
1130                         g_propagate_error (perror, EDB_ERROR_EX (OTHER_ERROR, _("No UID in the contact")));
1131                         g_object_unref (contact);
1132                         break;
1133                 }
1134
1135                 old_contact = load_contact (bf, txn, id, perror);
1136                 if (!old_contact) {
1137                         g_warning (G_STRLOC ": Failed to load contact %s", id);
1138                         status = STATUS_ERROR;
1139
1140                         g_free (id);
1141                         g_object_unref (contact);
1142                         break;
1143                 }
1144                 old_contacts = g_slist_prepend (old_contacts, old_contact);
1145
1146                 /* Transform incomming photo blobs to uris before storing this to the DB */
1147                 status = maybe_transform_vcard_for_photo (bf, old_contact, contact, NULL, perror);
1148                 if (status == STATUS_ERROR) {
1149                         g_warning (G_STRLOC ": Error transforming contact %s: %s",
1150                                    id, (perror && *perror) ? (*perror)->message : "Unknown Error");
1151
1152                         g_free (id);
1153                         g_object_unref (old_contact);
1154                         g_object_unref (contact);
1155                         break;
1156                 }
1157
1158                 /* update the revision (modified time of contact) */
1159                 set_revision (contact);
1160                 vcard_with_rev = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1161
1162                 /* This is disgusting, but for a time cards were added with
1163                  * ID's that are no longer used (they contained both the uri
1164                  * and the id.) If we recognize it as a uri (file:///...) trim
1165                  * off everything before the last '/', and use that as the
1166                  * id.*/
1167                 if (!strncmp (id, "file:///", strlen ("file:///"))) {
1168                         lookup_id = strrchr (id, '/') + 1;
1169                 }
1170                 else
1171                         lookup_id = id;
1172
1173                 string_to_dbt (lookup_id,      &id_dbt);
1174                 string_to_dbt (vcard_with_rev, &vcard_dbt);
1175
1176                 db_error = db->put (db, txn, &id_dbt, &vcard_dbt, 0);
1177                 g_free (vcard_with_rev);
1178
1179                 if (db_error != 0) {
1180                         g_warning (G_STRLOC ": db->put failed with %s", db_strerror (db_error));
1181                         db_error_to_gerror (db_error, perror);
1182
1183                         /* Abort as soon as a modification fails */
1184                         g_free (id);
1185                         g_object_unref (contact);
1186                         break;
1187                 }
1188
1189                 modified_contacts = g_slist_prepend (modified_contacts, contact);
1190                 ids = g_slist_prepend (ids, id);
1191         }
1192
1193         if (db_error == 0 && status != STATUS_ERROR) {
1194                 /* Commit transaction */
1195                 db_error = txn->commit (txn, 0);
1196                 if (db_error == 0) {
1197                         /* Flush cache information to disk */
1198                         if (db->sync (db, 0) != 0) {
1199                                 g_warning ("db->sync failed with %s", db_strerror (db_error));
1200                         }
1201                 } else {
1202                         g_warning (G_STRLOC ": txn->commit failed with %s", db_strerror (db_error));
1203                         db_error_to_gerror (db_error, perror);
1204                 }
1205         } else {
1206                 /* Rollback transaction */
1207                 txn->abort (txn);
1208         }
1209
1210         if (db_error == 0 && status != STATUS_ERROR) {
1211                 GError *error = NULL;
1212
1213                 /* Delete old photo file uris if need be (this will compare the new contact
1214                  * with the current copy in the BDB to extract the uris to delete) */
1215                 lold = old_contacts;
1216                 l = modified_contacts;
1217                 while (lold && l) {
1218                         maybe_delete_unused_uris (bf, E_CONTACT (lold->data), E_CONTACT (l->data));
1219                         lold = lold->next;
1220                         l = l->next;
1221                 }
1222
1223                 /* Update summary as well */
1224                 if (!e_book_backend_sqlitedb_remove_contacts (bf->priv->sqlitedb,
1225                                                               SQLITEDB_FOLDER_ID,
1226                                                               ids, &error)) {
1227                         g_warning ("Failed to remove contacts from the summary: %s", error->message);
1228                         g_error_free (error);
1229                 } else if (!e_book_backend_sqlitedb_add_contacts (bf->priv->sqlitedb,
1230                                                                   SQLITEDB_FOLDER_ID,
1231                                                                   modified_contacts, FALSE, &error)) {
1232                         g_warning ("Failed to add contacts to summary: %s", error->message);
1233                         g_error_free (error);
1234                 }
1235
1236                 *contacts = g_slist_reverse (modified_contacts);
1237         } else {
1238                 *contacts = NULL;
1239                 e_util_free_object_slist (modified_contacts);
1240         }
1241
1242         e_util_free_string_slist (ids);
1243         g_slist_free_full (old_contacts, g_object_unref);
1244
1245         e_book_backend_file_bump_revision (bf);
1246 }
1247
1248 static void
1249 e_book_backend_file_get_contact (EBookBackendSync *backend,
1250                                  EDataBook *book,
1251                                  GCancellable *cancellable,
1252                                  const gchar *id,
1253                                  gchar **vcard,
1254                                  GError **perror)
1255 {
1256         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1257
1258         if (!bf || !bf->priv || !bf->priv->file_db) {
1259                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
1260                 return;
1261         }
1262
1263         *vcard = load_vcard (bf, NULL, id, perror);
1264 }
1265
1266 static void
1267 e_book_backend_file_get_contact_list (EBookBackendSync *backend,
1268                                       EDataBook *book,
1269                                       GCancellable *cancellable,
1270                                       const gchar *query,
1271                                       GSList **contacts,
1272                                       GError **perror)
1273 {
1274         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1275         DB             *db = bf->priv->file_db;
1276         DBC            *dbc;
1277         gint            db_error;
1278         DBT  id_dbt, vcard_dbt;
1279         EBookBackendSExp *card_sexp = NULL;
1280         gboolean search_needed;
1281         const gchar *search = query;
1282         GSList *contact_list = NULL, *l;
1283         GSList *summary_list = NULL;
1284         gboolean searched_summary = FALSE;
1285         gboolean with_all_required_fields = FALSE;
1286
1287         d(printf ("e_book_backend_file_get_contact_list (%s)\n", search));
1288
1289         if (!db) {
1290                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
1291                 return;
1292         }
1293
1294         summary_list = e_book_backend_sqlitedb_search (bf->priv->sqlitedb,
1295                                                        SQLITEDB_FOLDER_ID,
1296                                                        search, NULL,
1297                                                        &searched_summary,
1298                                                        &with_all_required_fields, NULL);
1299
1300         if (summary_list) {
1301
1302                 for (l = summary_list; l; l = l->next) {
1303                         EbSdbSearchData *data = l->data;
1304
1305                         if (with_all_required_fields) {
1306                                 contact_list = g_slist_prepend (contact_list, data->vcard);
1307                                 data->vcard  = NULL;
1308                         } else {
1309                                 /* In this case the sqlitedb helped us with the query, but
1310                                  * the return information is incomplete so we need to load it up.
1311                                  */
1312                                 gchar *vcard;
1313
1314                                 vcard = load_vcard (bf, NULL, data->uid, perror);
1315
1316                                 /* Break out on the first BDB error */
1317                                 if (!vcard)
1318                                         break;
1319
1320                                 contact_list = g_slist_prepend (contact_list, vcard);
1321                         }
1322                 }
1323
1324                 g_slist_foreach (summary_list, (GFunc) e_book_backend_sqlitedb_search_data_free, NULL);
1325                 g_slist_free (summary_list);
1326
1327         } else {
1328                 search_needed = TRUE;
1329                 if (!strcmp (search, "(contains \"x-evolution-any-field\" \"\")"))
1330                         search_needed = FALSE;
1331
1332                 card_sexp = e_book_backend_sexp_new (search);
1333                 if (!card_sexp) {
1334                         g_propagate_error (perror, EDB_ERROR (INVALID_QUERY));
1335                         return;
1336                 }
1337
1338                 db_error = db->cursor (db, NULL, &dbc, 0);
1339
1340                 if (db_error != 0) {
1341                         g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
1342                         /* XXX this needs to be some CouldNotOpen error */
1343                         db_error_to_gerror (db_error, perror);
1344                         return;
1345                 }
1346
1347                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
1348                 vcard_dbt.flags = DB_DBT_MALLOC;
1349                 memset (&id_dbt, 0, sizeof (id_dbt));
1350                 db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_FIRST);
1351
1352                 while (db_error == 0) {
1353
1354                         /* don't include the version or revision in the list of cards */
1355                         if ((id_dbt.size != strlen (E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
1356                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) &&
1357                             (id_dbt.size != strlen (E_BOOK_BACKEND_FILE_REVISION_NAME) + 1
1358                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_REVISION_NAME))) {
1359
1360                                 if ((!search_needed) || (card_sexp != NULL && e_book_backend_sexp_match_vcard  (card_sexp, vcard_dbt.data))) {
1361                                         contact_list = g_slist_prepend (contact_list, vcard_dbt.data);
1362                                 } else {
1363                                         free (vcard_dbt.data);
1364                                 }
1365                         } else {
1366                                 free (vcard_dbt.data);
1367                         }
1368
1369                         db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_NEXT);
1370
1371                 }
1372                 g_object_unref (card_sexp);
1373
1374                 if (db_error == DB_NOTFOUND) {
1375                         /* Success */
1376                 } else {
1377                         g_warning (G_STRLOC ": dbc->c_get failed with %s", db_strerror (db_error));
1378                         db_error_to_gerror (db_error, perror);
1379                 }
1380
1381                 db_error = dbc->c_close (dbc);
1382                 if (db_error != 0) {
1383                         g_warning (G_STRLOC ": dbc->c_close failed with %s", db_strerror (db_error));
1384                 }
1385         }
1386
1387         *contacts = contact_list;
1388 }
1389
1390 static void
1391 e_book_backend_file_get_contact_list_uids (EBookBackendSync *backend,
1392                                            EDataBook *book,
1393                                            GCancellable *cancellable,
1394                                            const gchar *query,
1395                                            GSList **contacts_uids,
1396                                            GError **perror)
1397 {
1398         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1399         DB             *db = bf->priv->file_db;
1400         DBC            *dbc;
1401         gint            db_error;
1402         DBT  id_dbt, vcard_dbt;
1403         EBookBackendSExp *card_sexp = NULL;
1404         gboolean search_needed;
1405         const gchar *search = query;
1406         GSList *uids = NULL;
1407         gboolean searched = FALSE;
1408
1409         d(printf ("e_book_backend_file_get_contact_list (%s)\n", search));
1410
1411         if (!db) {
1412                 g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
1413                 return;
1414         }
1415
1416         uids = e_book_backend_sqlitedb_search_uids (bf->priv->sqlitedb,
1417                                                     SQLITEDB_FOLDER_ID,
1418                                                     search, &searched, NULL);
1419
1420         if (!searched) {
1421                 search_needed = TRUE;
1422                 if (!strcmp (search, "(contains \"x-evolution-any-field\" \"\")"))
1423                         search_needed = FALSE;
1424
1425                 card_sexp = e_book_backend_sexp_new (search);
1426                 if (!card_sexp) {
1427                         g_propagate_error (perror, EDB_ERROR (INVALID_QUERY));
1428                         return;
1429                 }
1430
1431                 db_error = db->cursor (db, NULL, &dbc, 0);
1432
1433                 if (db_error != 0) {
1434                         g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
1435                         /* XXX this needs to be some CouldNotOpen error */
1436                         db_error_to_gerror (db_error, perror);
1437                         return;
1438                 }
1439
1440                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
1441                 vcard_dbt.flags = DB_DBT_MALLOC;
1442                 memset (&id_dbt, 0, sizeof (id_dbt));
1443                 db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_FIRST);
1444
1445                 while (db_error == 0) {
1446
1447                         /* don't include the version or revision in the list of cards */
1448                         if ((id_dbt.size != strlen (E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
1449                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) &&
1450                             (id_dbt.size != strlen (E_BOOK_BACKEND_FILE_REVISION_NAME) + 1
1451                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_REVISION_NAME))) {
1452
1453                                 if ((!search_needed) || (card_sexp != NULL && e_book_backend_sexp_match_vcard  (card_sexp, vcard_dbt.data))) {
1454                                         uids = g_slist_prepend (uids, g_strdup (id_dbt.data));
1455                                 }
1456                         }
1457
1458                         g_free (vcard_dbt.data);
1459
1460                         db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_NEXT);
1461
1462                 }
1463                 g_object_unref (card_sexp);
1464
1465                 if (db_error == DB_NOTFOUND) {
1466                         /* Success */
1467                 } else {
1468                         g_warning (G_STRLOC ": dbc->c_get failed with %s", db_strerror (db_error));
1469                         db_error_to_gerror (db_error, perror);
1470                 }
1471
1472                 db_error = dbc->c_close (dbc);
1473                 if (db_error != 0) {
1474                         g_warning (G_STRLOC ": dbc->c_close failed with %s", db_strerror (db_error));
1475                 }
1476         }
1477
1478         *contacts_uids = g_slist_reverse (uids);
1479 }
1480
1481 typedef struct {
1482         EBookBackendFile *bf;
1483         GThread *thread;
1484         EFlag *running;
1485 } FileBackendSearchClosure;
1486
1487 static void
1488 closure_destroy (FileBackendSearchClosure *closure)
1489 {
1490         d(printf ("destroying search closure\n"));
1491         e_flag_free (closure->running);
1492         g_free (closure);
1493 }
1494
1495 static FileBackendSearchClosure *
1496 init_closure (EDataBookView *book_view,
1497               EBookBackendFile *bf)
1498 {
1499         FileBackendSearchClosure *closure = g_new (FileBackendSearchClosure, 1);
1500
1501         closure->bf = bf;
1502         closure->thread = NULL;
1503         closure->running = e_flag_new ();
1504
1505         g_object_set_data_full (G_OBJECT (book_view), "EBookBackendFile.BookView::closure",
1506                                 closure, (GDestroyNotify) closure_destroy);
1507
1508         return closure;
1509 }
1510
1511 static FileBackendSearchClosure *
1512 get_closure (EDataBookView *book_view)
1513 {
1514         return g_object_get_data (G_OBJECT (book_view), "EBookBackendFile.BookView::closure");
1515 }
1516
1517 static void
1518 notify_update_vcard (EDataBookView *book_view,
1519                      gboolean prefiltered,
1520                      const gchar *id,
1521                      const gchar *vcard)
1522 {
1523         if (prefiltered)
1524                 e_data_book_view_notify_update_prefiltered_vcard (book_view, id, vcard);
1525         else
1526                 e_data_book_view_notify_update_vcard (book_view, id, vcard);
1527 }
1528
1529 static gpointer
1530 book_view_thread (gpointer data)
1531 {
1532         EDataBookView *book_view;
1533         FileBackendSearchClosure *closure;
1534         EBookBackendFile *bf;
1535         const gchar *query;
1536         DB  *db;
1537         DBT id_dbt, vcard_dbt;
1538         gint db_error;
1539         gboolean allcontacts;
1540         GSList *summary_list, *l;
1541         GHashTable *fields_of_interest;
1542         gboolean searched = FALSE;
1543         gboolean with_all_required_fields = FALSE;
1544
1545         g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (data), NULL);
1546
1547         book_view = data;
1548         closure = get_closure (book_view);
1549         if (!closure) {
1550                 g_warning (G_STRLOC ": NULL closure in book view thread");
1551                 return NULL;
1552         }
1553         bf = closure->bf;
1554
1555         d(printf ("starting initial population of book view\n"));
1556
1557         /* ref the book view because it'll be removed and unrefed
1558          * when/if it's stopped */
1559         e_data_book_view_ref (book_view);
1560
1561         db                 = bf->priv->file_db;
1562         query              = e_data_book_view_get_card_query (book_view);
1563         fields_of_interest = e_data_book_view_get_fields_of_interest (book_view);
1564
1565         if (!db) {
1566                 e_data_book_view_notify_complete (book_view, EDB_NOT_OPENED_ERROR);
1567                 e_data_book_view_unref (book_view);
1568                 return NULL;
1569         }
1570
1571         if ( !strcmp (query, "(contains \"x-evolution-any-field\" \"\")")) {
1572                 e_data_book_view_notify_progress (book_view, -1, _("Loading..."));
1573                 allcontacts = TRUE;
1574         } else {
1575                 e_data_book_view_notify_progress (book_view, -1, _("Searching..."));
1576                 allcontacts = FALSE;
1577         }
1578
1579         d(printf ("signalling parent thread\n"));
1580         e_flag_set (closure->running);
1581
1582         summary_list = e_book_backend_sqlitedb_search (bf->priv->sqlitedb,
1583                                                        SQLITEDB_FOLDER_ID,
1584                                                        query, fields_of_interest,
1585                                                        &searched, &with_all_required_fields, NULL);
1586
1587         if (searched) {
1588
1589                 for (l = summary_list; l; l = l->next) {
1590                         EbSdbSearchData *data = l->data;
1591                         gchar *vcard = NULL;
1592
1593                         if (with_all_required_fields) {
1594                                 vcard = data->vcard;
1595                                 data->vcard = NULL;
1596                         } else {
1597                                 GError *error = NULL;
1598
1599                                 /* The sqlitedb summary did not satisfy 'fields-of-interest',
1600                                  * load the complete vcard here. */
1601                                 vcard = load_vcard (bf, NULL, data->uid, &error);
1602
1603                                 if (error) {
1604                                         g_warning ("Error loading contact %s: %s",
1605                                                    data->uid, error->message);
1606                                         g_error_free (error);
1607                                 }
1608
1609                                 if (!vcard)
1610                                         continue;
1611
1612                         }
1613
1614                         notify_update_vcard (book_view, TRUE, data->uid, vcard);
1615                         g_free (vcard);
1616                 }
1617
1618                 g_slist_foreach (summary_list, (GFunc) e_book_backend_sqlitedb_search_data_free, NULL);
1619                 g_slist_free (summary_list);
1620         } else {
1621                 /* iterate over the db and do the query there */
1622                 DBC    *dbc;
1623
1624                 memset (&id_dbt, 0, sizeof (id_dbt));
1625                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
1626                 vcard_dbt.flags = DB_DBT_MALLOC;
1627
1628                 db_error = db->cursor (db, NULL, &dbc, 0);
1629                 if (db_error == 0) {
1630
1631                         db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_FIRST);
1632                         while (db_error == 0) {
1633
1634                                 if (!e_flag_is_set (closure->running))
1635                                         break;
1636
1637                                 /* don't include the version in the list of cards */
1638                                 if (strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME) &&
1639                                     strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_REVISION_NAME)) {
1640                                         notify_update_vcard (book_view, allcontacts,
1641                                                              id_dbt.data, vcard_dbt.data);
1642                                 }
1643
1644                                 g_free (vcard_dbt.data);
1645                                 db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_NEXT);
1646                         }
1647
1648                         dbc->c_close (dbc);
1649                         if (db_error && db_error != DB_NOTFOUND)
1650                                 g_warning ("e_book_backend_file_search: error building list: %s",
1651                                            db_strerror (db_error));
1652                 }
1653                 else if (db_error == DB_RUNRECOVERY) {
1654                         g_warning ("e_book_backend_file_search: error getting the cursor for %s",
1655                                    bf->priv->filename);
1656                         abort ();
1657                 }
1658
1659         }
1660
1661         if (e_flag_is_set (closure->running))
1662                 e_data_book_view_notify_complete (book_view, NULL /* Success */);
1663
1664         e_data_book_view_unref (book_view);
1665
1666         d(printf ("finished population of book view\n"));
1667
1668         return NULL;
1669 }
1670
1671 static void
1672 e_book_backend_file_start_book_view (EBookBackend *backend,
1673                                      EDataBookView *book_view)
1674 {
1675         FileBackendSearchClosure *closure = init_closure (book_view, E_BOOK_BACKEND_FILE (backend));
1676
1677         d(printf ("starting book view thread\n"));
1678         closure->thread = g_thread_create (book_view_thread, book_view, TRUE, NULL);
1679
1680         e_flag_wait (closure->running);
1681
1682         /* at this point we know the book view thread is actually running */
1683         d(printf ("returning from start_book_view\n"));
1684 }
1685
1686 static void
1687 e_book_backend_file_stop_book_view (EBookBackend *backend,
1688                                     EDataBookView *book_view)
1689 {
1690         FileBackendSearchClosure *closure = get_closure (book_view);
1691         gboolean need_join;
1692
1693         if (!closure)
1694                 return;
1695
1696         d(printf ("stopping query\n"));
1697         need_join = e_flag_is_set (closure->running);
1698         e_flag_clear (closure->running);
1699
1700         if (need_join)
1701                 g_thread_join (closure->thread);
1702 }
1703
1704 static void
1705 e_book_backend_file_authenticate_user (EBookBackendSync *backend,
1706                                        GCancellable *cancellable,
1707                                        ECredentials *credentials,
1708                                        GError **perror)
1709 {
1710         /* Success */
1711 }
1712
1713 /*
1714 ** versions:
1715 **
1716 ** 0.0 just a list of cards
1717 **
1718 ** 0.1 same as 0.0, but with the version tag
1719 **
1720 ** 0.2 not a real format upgrade, just a hack to fix broken ids caused
1721 **     by a bug in early betas, but we only need to convert them if
1722 **     the previous version is 0.1, since the bug existed after 0.1
1723 **     came about.
1724 */
1725 static gboolean
1726 e_book_backend_file_upgrade_db (EBookBackendFile *bf,
1727                                 gchar *old_version)
1728 {
1729         DB  *db = bf->priv->file_db;
1730         gint db_error;
1731         DBT version_name_dbt, version_dbt;
1732
1733         if (!db) {
1734                 g_warning (G_STRLOC ": No DB opened");
1735                 return FALSE;
1736         }
1737
1738         if (strcmp (old_version, "0.0")
1739             && strcmp (old_version, "0.1")) {
1740                 g_warning ("unsupported version '%s' found in PAS backend file\n",
1741                            old_version);
1742                 return FALSE;
1743         }
1744
1745         if (!strcmp (old_version, "0.1")) {
1746                 /* we just loop through all the cards in the db,
1747                  * giving them valid ids if they don't have them */
1748                 DBT  id_dbt, vcard_dbt;
1749                 DBC *dbc;
1750                 gint  card_failed = 0;
1751
1752                 db_error = db->cursor (db, NULL, &dbc, 0);
1753                 if (db_error != 0) {
1754                         g_warning (G_STRLOC ": db->cursor failed with %s", db_strerror (db_error));
1755                         return FALSE;
1756                 }
1757
1758                 memset (&id_dbt, 0, sizeof (id_dbt));
1759                 memset (&vcard_dbt, 0, sizeof (vcard_dbt));
1760
1761                 db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_FIRST);
1762
1763                 while (db_error == 0) {
1764                         if ((id_dbt.size != strlen (E_BOOK_BACKEND_FILE_VERSION_NAME) + 1
1765                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_VERSION_NAME)) &&
1766                             (id_dbt.size != strlen (E_BOOK_BACKEND_FILE_REVISION_NAME) + 1
1767                              || strcmp (id_dbt.data, E_BOOK_BACKEND_FILE_REVISION_NAME))) {
1768                                 EContact *contact;
1769
1770                                 contact = create_contact (id_dbt.data, vcard_dbt.data);
1771
1772                                 /* the cards we're looking for are
1773                                  * created with a normal id dbt, but
1774                                  * with the id field in the vcard set
1775                                  * to something that doesn't match.
1776                                  * so, we need to modify the card to
1777                                  * have the same id as the the dbt. */
1778                                 if (strcmp (id_dbt.data, e_contact_get_const (contact, E_CONTACT_UID))) {
1779                                         gchar *vcard;
1780
1781                                         e_contact_set (contact, E_CONTACT_UID, id_dbt.data);
1782
1783                                         vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1784                                         string_to_dbt (vcard, &vcard_dbt);
1785
1786                                         db_error = db->put (db, NULL,
1787                                                             &id_dbt, &vcard_dbt, 0);
1788
1789                                         g_free (vcard);
1790
1791                                         if (db_error != 0)
1792                                                 card_failed++;
1793                                 }
1794
1795                                 g_object_unref (contact);
1796                         }
1797
1798                         db_error = dbc->c_get (dbc, &id_dbt, &vcard_dbt, DB_NEXT);
1799                 }
1800
1801                 dbc->c_close (dbc);
1802
1803                 if (card_failed) {
1804                         g_warning ("failed to update %d cards", card_failed);
1805                         return FALSE;
1806                 }
1807         }
1808
1809         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION_NAME, &version_name_dbt);
1810         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION, &version_dbt);
1811
1812         db_error = db->put (db, NULL, &version_name_dbt, &version_dbt, 0);
1813         if (db_error == 0)
1814                 return TRUE;
1815         else
1816                 return FALSE;
1817 }
1818
1819 static gboolean
1820 e_book_backend_file_maybe_upgrade_db (EBookBackendFile *bf)
1821 {
1822         DB   *db = bf->priv->file_db;
1823         DBT  version_name_dbt, version_dbt;
1824         gint  db_error;
1825         gchar *version;
1826         gboolean ret_val = TRUE;
1827
1828         if (!db) {
1829                 g_warning (G_STRLOC ": No DB opened");
1830                 return FALSE;
1831         }
1832
1833         string_to_dbt (E_BOOK_BACKEND_FILE_VERSION_NAME, &version_name_dbt);
1834         memset (&version_dbt, 0, sizeof (version_dbt));
1835         version_dbt.flags = DB_DBT_MALLOC;
1836
1837         db_error = db->get (db, NULL, &version_name_dbt, &version_dbt, 0);
1838         if (db_error == 0) {
1839                 /* success */
1840                 version = version_dbt.data;
1841         }
1842         else {
1843                 /* key was not in file */
1844                 version = g_strdup ("0.0");
1845         }
1846
1847         if (strcmp (version, E_BOOK_BACKEND_FILE_VERSION))
1848                 ret_val = e_book_backend_file_upgrade_db (bf, version);
1849
1850         g_free (version);
1851
1852         return ret_val;
1853 }
1854
1855 #ifdef CREATE_DEFAULT_VCARD
1856 # include <libedata-book/ximian-vcard.h>
1857 #endif
1858
1859 static void
1860 #if (DB_VERSION_MAJOR > 4) || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
1861 file_errcall (const DB_ENV *env,
1862               const gchar *buf1,
1863               const gchar *buf2)
1864 #else
1865 file_errcall (const gchar *buf1,
1866               gchar *buf2)
1867 #endif
1868 {
1869         g_warning ("libdb error: %s", buf2);
1870 }
1871
1872 static void
1873 e_book_backend_file_open (EBookBackendSync *backend,
1874                           EDataBook *book,
1875                           GCancellable *cancellable,
1876                           gboolean only_if_exists,
1877                           GError **perror)
1878 {
1879         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
1880         gchar            *dirname, *filename;
1881         gboolean          readonly = TRUE;
1882         ESource          *source;
1883         gint              db_error;
1884         DB               *db;
1885         DB_ENV           *env;
1886         GError           *local_error = NULL;
1887         global_env       *genv = NULL;
1888
1889 #ifdef CREATE_DEFAULT_VCARD
1890         gboolean create_default_vcard = FALSE;
1891 #endif
1892
1893         source = e_backend_get_source (E_BACKEND (backend));
1894         dirname = e_book_backend_file_extract_path_from_source (
1895                 source, GET_PATH_DB_DIR);
1896         filename = g_build_filename (dirname, "addressbook.db", NULL);
1897
1898         db_error = e_db3_utils_maybe_recover (filename);
1899         if (db_error != 0) {
1900                 g_warning ("db recovery failed with %s", db_strerror (db_error));
1901                 g_free (dirname);
1902                 g_free (filename);
1903                 db_error_to_gerror (db_error, perror);
1904                 return;
1905         }
1906
1907         G_LOCK (db_environments);
1908         if (db_environments) {
1909                 genv = g_hash_table_lookup (db_environments, dirname);
1910         }
1911         if (genv && genv->ref_count > 0) {
1912                 genv->ref_count++;
1913                 env = genv->env;
1914         } else {
1915                 db_error = db_env_create (&env, 0);
1916                 if (db_error != 0) {
1917                         g_warning ("db_env_create failed with %s", db_strerror (db_error));
1918                         G_UNLOCK (db_environments);
1919                         g_free (dirname);
1920                         g_free (filename);
1921                         db_error_to_gerror (db_error, perror);
1922                         return;
1923                 }
1924
1925                 env->set_errcall (env, file_errcall);
1926
1927                 /* Set the allocation routines to the non-aborting GLib functions */
1928                 env->set_alloc (env, (gpointer (*)(gsize)) g_try_malloc,
1929                                 (gpointer (*)(gpointer , gsize)) g_try_realloc,
1930                                 g_free);
1931
1932                 /* Make sure the database directory is created
1933                  * or env->open will fail */
1934                 if (!only_if_exists) {
1935                         if (!create_directory (dirname, perror)) {
1936                                 g_warning ("failed to create directory at %s", dirname);
1937                                 G_UNLOCK (db_environments);
1938                                 g_free (dirname);
1939                                 g_free (filename);
1940                                 return;
1941                         }
1942                 }
1943
1944                 /*
1945                  * DB_INIT_TXN enables transaction support. It requires DB_INIT_LOCK to
1946                  * initialize the locking subsystem and DB_INIT_LOG for the logging
1947                  * subsystem.
1948                  *
1949                  * DB_INIT_MPOOL enables the in-memory cache.
1950                  *
1951                  * Note that we need either DB_INIT_CDB or DB_INIT_LOCK, because we will
1952                  * have multiple threads reading and writing concurrently without
1953                  * any locking above libdb. Right now DB_INIT_LOCK is used because
1954                  * DB_INIT_TXN conflicts with DB_INIT_CDB.
1955                  */
1956                 db_error = (*env->open) (env, dirname, DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_THREAD, 0);
1957                 if (db_error != 0) {
1958                         env->close (env, 0);
1959                         g_warning ("db_env_open failed with %s", db_strerror (db_error));
1960                         G_UNLOCK (db_environments);
1961                         g_free (dirname);
1962                         g_free (filename);
1963                         db_error_to_gerror (db_error, perror);
1964                         return;
1965                 }
1966
1967                 /* Insert in the db_environments hash table */
1968                 if (!db_environments) {
1969                         db_environments = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1970                 }
1971                 genv = g_malloc0 (sizeof (global_env));
1972                 genv->ref_count = 1;
1973                 genv->env = env;
1974                 g_hash_table_insert (db_environments, g_strdup (dirname), genv);
1975         }
1976         G_UNLOCK (db_environments);
1977
1978         bf->priv->env = env;
1979
1980         db_error = db_create (&db, env, 0);
1981         if (db_error != 0) {
1982                 g_warning ("db_create failed with %s", db_strerror (db_error));
1983                 g_free (dirname);
1984                 g_free (filename);
1985                 db_error_to_gerror (db_error, perror);
1986                 return;
1987         }
1988
1989         db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD | DB_AUTO_COMMIT, 0666);
1990
1991         if (db_error == DB_OLD_VERSION) {
1992                 db_error = e_db3_utils_upgrade_format (filename);
1993
1994                 if (db_error != 0) {
1995                         g_warning ("db format upgrade failed with %s", db_strerror (db_error));
1996                         g_free (dirname);
1997                         g_free (filename);
1998                         db_error_to_gerror (db_error, perror);
1999                         return;
2000                 }
2001
2002                 db->close (db, 0);
2003                 db_error = db_create (&db, env, 0);
2004                 if (db_error != 0) {
2005                         g_warning ("db_create failed with %s", db_strerror (db_error));
2006                         g_free (dirname);
2007                         g_free (filename);
2008                         db_error_to_gerror (db_error, perror);
2009                         return;
2010                 }
2011
2012                 db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD | DB_AUTO_COMMIT, 0666);
2013         }
2014
2015         if (db_error == 0) {
2016                 readonly = FALSE;
2017         } else {
2018                 db->close (db, 0);
2019                 db_error = db_create (&db, env, 0);
2020                 if (db_error != 0) {
2021                         g_warning ("db_create failed with %s", db_strerror (db_error));
2022                         g_free (dirname);
2023                         g_free (filename);
2024                         db_error_to_gerror (db_error, perror);
2025                         return;
2026                 }
2027
2028                 db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_RDONLY | DB_THREAD | DB_AUTO_COMMIT, 0666);
2029
2030                 if (db_error != 0 && !only_if_exists) {
2031
2032                         /* the database didn't exist, so we create the
2033                          * directory then the .db */
2034                         db->close (db, 0);
2035
2036                         if (!create_directory (dirname, perror)) {
2037                                 g_free (dirname);
2038                                 g_free (filename);
2039                                 return;
2040                         }
2041
2042                         db_error = db_create (&db, env, 0);
2043                         if (db_error != 0) {
2044                                 g_warning ("db_create failed with %s", db_strerror (db_error));
2045                                 g_free (dirname);
2046                                 g_free (filename);
2047                                 db_error_to_gerror (db_error, perror);
2048                                 return;
2049                         }
2050
2051                         db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_CREATE | DB_THREAD | DB_AUTO_COMMIT, 0666);
2052                         if (db_error != 0) {
2053                                 db->close (db, 0);
2054                                 g_warning ("db->open (... %s ... DB_CREATE ...) failed with %s", filename, db_strerror (db_error));
2055                         }
2056                         else {
2057 #ifdef CREATE_DEFAULT_VCARD
2058                                 create_default_vcard = TRUE;
2059 #endif
2060
2061                                 readonly = FALSE;
2062                         }
2063                 }
2064         }
2065
2066         bf->priv->file_db = db;
2067
2068         if (db_error != 0) {
2069                 bf->priv->file_db = NULL;
2070                 g_free (dirname);
2071                 g_free (filename);
2072                 db_error_to_gerror (db_error, perror);
2073                 return;
2074         }
2075
2076 #ifdef CREATE_DEFAULT_VCARD
2077         if (create_default_vcard) {
2078                 GSList l;
2079                 l.data = XIMIAN_VCARD;
2080                 l.next = NULL;
2081
2082                 if (!do_create (bf, &l, NULL, NULL))
2083                         g_warning ("Cannot create default contact");
2084         }
2085 #endif
2086
2087         if (!e_book_backend_file_maybe_upgrade_db (bf)) {
2088                 db->close (db, 0);
2089                 bf->priv->file_db = NULL;
2090                 g_free (dirname);
2091                 g_free (filename);
2092                 g_propagate_error (perror, EDB_ERROR_EX (OTHER_ERROR, "e_book_backend_file_maybe_upgrade_db failed"));
2093                 return;
2094         }
2095
2096         g_free (bf->priv->dirname);
2097         g_free (bf->priv->filename);
2098         bf->priv->dirname = dirname;
2099         bf->priv->filename = filename;
2100
2101         bf->priv->sqlitedb = e_book_backend_sqlitedb_new (bf->priv->dirname,
2102                                                           SQLITEDB_EMAIL_ID,
2103                                                           SQLITEDB_FOLDER_ID,
2104                                                           SQLITEDB_FOLDER_NAME,
2105                                                           FALSE,
2106                                                           perror);
2107         if (!bf->priv->sqlitedb)
2108                 return;
2109
2110         if (!e_book_backend_sqlitedb_get_is_populated (bf->priv->sqlitedb,
2111                                                        SQLITEDB_FOLDER_ID,
2112                                                        &local_error)) {
2113                 if (local_error) {
2114                         g_propagate_error (perror, local_error);
2115                         return;
2116                 } else if (!build_sqlitedb (bf->priv)) {
2117                         g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR,
2118                                              _("Failed to build summary for an address book %s"),
2119                                      bf->priv->filename));
2120                 }
2121         }
2122
2123         /* Resolve the photo directory here */
2124         dirname = e_book_backend_file_extract_path_from_source (
2125                 source, GET_PATH_PHOTO_DIR);
2126         if (!only_if_exists && !create_directory (dirname, perror))
2127                 return;
2128         bf->priv->photo_dirname = dirname;
2129
2130         e_book_backend_file_load_revision (bf);
2131
2132         e_book_backend_notify_online (E_BOOK_BACKEND (backend), TRUE);
2133         e_book_backend_notify_readonly (E_BOOK_BACKEND (backend), readonly);
2134         e_book_backend_notify_opened (E_BOOK_BACKEND (backend), NULL /* Success */);
2135
2136         e_book_backend_notify_property_changed (E_BOOK_BACKEND (backend),
2137                                                 BOOK_BACKEND_PROPERTY_REVISION,
2138                                                 bf->priv->revision);
2139 }
2140
2141 static gboolean
2142 select_changes (const gchar *name)
2143 {
2144         gchar *p;
2145
2146         if (strlen (name) < strlen (CHANGES_DB_SUFFIX))
2147                 return FALSE;
2148
2149         p = strstr (name, CHANGES_DB_SUFFIX);
2150         if (!p)
2151                 return FALSE;
2152
2153         if (strlen (p) != strlen (CHANGES_DB_SUFFIX))
2154                 return FALSE;
2155
2156         return TRUE;
2157 }
2158
2159 static void
2160 remove_photos (const gchar *dirname)
2161 {
2162         GDir *dir;
2163
2164         dir = g_dir_open (dirname, 0, NULL);
2165         if (dir) {
2166                 const gchar *name;
2167
2168                 while ((name = g_dir_read_name (dir))) {
2169                         gchar *full_path = g_build_filename (dirname, name, NULL);
2170                         if (-1 == g_unlink (full_path)) {
2171                                 g_warning ("failed to remove photo file %s: %s",
2172                                            full_path, g_strerror (errno));
2173                         }
2174                         g_free (full_path);
2175                 }
2176
2177                 g_dir_close (dir);
2178         }
2179 }
2180
2181 static void
2182 e_book_backend_file_remove (EBookBackendSync *backend,
2183                             EDataBook *book,
2184                             GCancellable *cancellable,
2185                             GError **perror)
2186 {
2187         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
2188         GDir *dir;
2189
2190         if (!remove_file (bf->priv->filename, perror))
2191                 return;
2192
2193         if (!e_book_backend_sqlitedb_remove (bf->priv->sqlitedb, perror))
2194                 return;
2195
2196         /* unref the sqlitedb before we remove the file so it's not written out again */
2197         g_object_unref (bf->priv->sqlitedb);
2198         bf->priv->sqlitedb = NULL;
2199
2200         dir = g_dir_open (bf->priv->dirname, 0, NULL);
2201         if (dir) {
2202                 const gchar *name;
2203
2204                 while ((name = g_dir_read_name (dir))) {
2205                         if (select_changes (name)) {
2206                                 gchar *full_path = g_build_filename (bf->priv->dirname, name, NULL);
2207                                 if (g_unlink (full_path) == -1) {
2208                                         g_warning ("failed to remove change db `%s': %s", full_path, g_strerror (errno));
2209                                 }
2210                                 g_free (full_path);
2211                         }
2212                 }
2213
2214                 g_dir_close (dir);
2215         }
2216
2217         /* Remove photos and the photo directory */
2218         remove_photos (bf->priv->photo_dirname);
2219         if (-1 == g_rmdir (bf->priv->photo_dirname))
2220                 g_warning ("failed to remove directory `%s`: %s", bf->priv->photo_dirname, g_strerror (errno));
2221
2222         /* Try removing the base directory now */
2223         if (-1 == g_rmdir (bf->priv->dirname))
2224                 g_warning ("failed to remove directory `%s`: %s", bf->priv->dirname, g_strerror (errno));
2225
2226         /* we may not have actually succeeded in removing the
2227          * backend's files/dirs, but there's nothing we can do about
2228          * it here..  the only time we should return failure is if we
2229          * failed to remove the actual data.  a failure should mean
2230          * that the addressbook is still valid */
2231 }
2232
2233 static gboolean
2234 e_book_backend_file_get_backend_property (EBookBackendSync *backend,
2235                                           EDataBook *book,
2236                                           GCancellable *cancellable,
2237                                           const gchar *prop_name,
2238                                           gchar **prop_value,
2239                                           GError **error)
2240 {
2241         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
2242         gboolean processed = TRUE;
2243
2244         g_return_val_if_fail (prop_name != NULL, FALSE);
2245         g_return_val_if_fail (prop_value != NULL, FALSE);
2246
2247         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2248                 *prop_value = g_strdup ("local,do-initial-query,bulk-adds,bulk-modifies,bulk-removes,contact-lists");
2249         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
2250                 *prop_value = g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
2251         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
2252                 GSList *fields = NULL;
2253                 gint i;
2254
2255                 /* XXX we need a way to say "we support everything", since the
2256                  * file backend does */
2257                 for (i = 1; i < E_CONTACT_FIELD_LAST; i++)
2258                         fields = g_slist_append (fields, (gpointer) e_contact_field_name (i));
2259
2260                 *prop_value = e_data_book_string_slist_to_comma_string (fields);
2261                 g_slist_free (fields);
2262         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_AUTH_METHODS)) {
2263                 *prop_value = NULL;
2264         } else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REVISION)) {
2265                 *prop_value = g_strdup (bf->priv->revision);
2266         } else {
2267                 processed = FALSE;
2268         }
2269
2270         return processed;
2271 }
2272
2273 static void
2274 e_book_backend_file_notify_online_cb (EBookBackend *backend,
2275                                       GParamSpec *pspec)
2276 {
2277         if (e_book_backend_is_opened (backend))
2278                 e_book_backend_notify_online (backend, TRUE);
2279 }
2280
2281 static void
2282 e_book_backend_file_sync (EBookBackend *backend)
2283 {
2284         EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
2285         gint db_error;
2286
2287         g_return_if_fail (bf != NULL);
2288
2289         if (bf->priv->file_db) {
2290                 db_error = bf->priv->file_db->sync (bf->priv->file_db, 0);
2291                 if (db_error != 0)
2292                         g_warning (G_STRLOC ": db->sync failed with %s", db_strerror (db_error));
2293         }
2294 }
2295
2296 typedef struct {
2297         EContact         *contact;
2298         EBookBackendFile *bf;
2299 } NotifyData;
2300
2301 static gboolean
2302 view_notify_update (EDataBookView *view,
2303                     gpointer data)
2304 {
2305         NotifyData *ndata    = data;
2306         GHashTable *fields   = e_data_book_view_get_fields_of_interest (view);
2307         gboolean    notified = FALSE;
2308         gboolean    with_all_required_fields = FALSE;
2309
2310         if (e_book_backend_sqlitedb_is_summary_query (e_data_book_view_get_card_query (view)) &&
2311             e_book_backend_sqlitedb_is_summary_fields (fields)) {
2312
2313                 const gchar *uid = e_contact_get_const (ndata->contact, E_CONTACT_UID);
2314                 gchar       *vcard;
2315
2316                 vcard = e_book_backend_sqlitedb_get_vcard_string (ndata->bf->priv->sqlitedb,
2317                                                                   SQLITEDB_FOLDER_ID, uid,
2318                                                                   fields, &with_all_required_fields, NULL);
2319
2320                 if (vcard) {
2321                         if (with_all_required_fields) {
2322                                 e_data_book_view_notify_update_prefiltered_vcard (view, uid, vcard);
2323                                 notified = TRUE;
2324                         }
2325                         g_free (vcard);
2326                 }
2327         }
2328
2329         if (!notified)
2330                 e_data_book_view_notify_update (view, ndata->contact);
2331
2332         return TRUE;
2333 }
2334
2335 static void
2336 e_book_backend_file_notify_update (EBookBackend *backend,
2337                                    const EContact *contact)
2338 {
2339         NotifyData data = { (EContact *) contact, E_BOOK_BACKEND_FILE (backend) };
2340
2341         e_book_backend_foreach_view (backend, view_notify_update, &data);
2342 }
2343
2344 static void
2345 e_book_backend_file_dispose (GObject *object)
2346 {
2347         EBookBackendFile *bf;
2348         global_env *genv;
2349
2350         bf = E_BOOK_BACKEND_FILE (object);
2351
2352         if (bf->priv->file_db) {
2353                 bf->priv->file_db->close (bf->priv->file_db, 0);
2354                 bf->priv->file_db = NULL;
2355         }
2356
2357         G_LOCK (db_environments);
2358         genv = g_hash_table_lookup (db_environments, bf->priv->dirname);
2359         if (genv) {
2360                 genv->ref_count--;
2361                 if (genv->ref_count == 0) {
2362                         genv->env->close (genv->env, 0);
2363                         g_free (genv);
2364                         g_hash_table_remove (db_environments, bf->priv->dirname);
2365                 }
2366                 if (g_hash_table_size (db_environments) == 0) {
2367                         g_hash_table_destroy (db_environments);
2368                         db_environments = NULL;
2369                 }
2370         }
2371         G_UNLOCK (db_environments);
2372
2373         if (bf->priv->sqlitedb) {
2374                 g_object_unref (bf->priv->sqlitedb);
2375                 bf->priv->sqlitedb = NULL;
2376         }
2377
2378         G_OBJECT_CLASS (e_book_backend_file_parent_class)->dispose (object);
2379 }
2380
2381 static void
2382 e_book_backend_file_finalize (GObject *object)
2383 {
2384         EBookBackendFilePrivate *priv;
2385
2386         priv = E_BOOK_BACKEND_FILE_GET_PRIVATE (object);
2387
2388         g_free (priv->filename);
2389         g_free (priv->dirname);
2390         g_free (priv->photo_dirname);
2391         g_free (priv->revision);
2392
2393         /* Chain up to parent's finalize() method. */
2394         G_OBJECT_CLASS (e_book_backend_file_parent_class)->finalize (object);
2395 }
2396
2397 #ifdef G_OS_WIN32
2398 /* Avoid compiler warning by providing a function with exactly the
2399  * prototype that db_env_set_func_open() wants for the open method.
2400  */
2401
2402 static gint
2403 my_open (const gchar *name,
2404          gint oflag,
2405          ...)
2406 {
2407         gint mode = 0;
2408
2409         if (oflag & O_CREAT) {
2410                 va_list arg;
2411                 va_start (arg, oflag);
2412                 mode = va_arg (arg, gint);
2413                 va_end (arg);
2414         }
2415
2416         return g_open (name, oflag, mode);
2417 }
2418
2419 gint
2420 my_rename (const gchar *oldname,
2421            const gchar *newname)
2422 {
2423         return g_rename (oldname, newname);
2424 }
2425
2426 gint
2427 my_exists (const gchar *name,
2428            gint *isdirp)
2429 {
2430         if (!g_file_test (name, G_FILE_TEST_EXISTS))
2431                 return ENOENT;
2432         if (isdirp != NULL)
2433                 *isdirp = g_file_test (name, G_FILE_TEST_IS_DIR);
2434         return 0;
2435 }
2436
2437 gint
2438 my_unlink (const gchar *name)
2439 {
2440         return g_unlink (name);
2441 }
2442
2443 #endif
2444
2445 static void
2446 e_book_backend_file_class_init (EBookBackendFileClass *class)
2447 {
2448         GObjectClass    *object_class = G_OBJECT_CLASS (class);
2449         EBookBackendSyncClass *sync_class;
2450         EBookBackendClass *backend_class;
2451
2452         g_type_class_add_private (class, sizeof (EBookBackendFilePrivate));
2453
2454         sync_class = E_BOOK_BACKEND_SYNC_CLASS (class);
2455         backend_class = E_BOOK_BACKEND_CLASS (class);
2456
2457         /* Set the virtual methods. */
2458         backend_class->start_book_view          = e_book_backend_file_start_book_view;
2459         backend_class->stop_book_view           = e_book_backend_file_stop_book_view;
2460         backend_class->sync                     = e_book_backend_file_sync;
2461         backend_class->notify_update            = e_book_backend_file_notify_update;
2462
2463         sync_class->open_sync                   = e_book_backend_file_open;
2464         sync_class->remove_sync                 = e_book_backend_file_remove;
2465         sync_class->get_backend_property_sync   = e_book_backend_file_get_backend_property;
2466         sync_class->create_contacts_sync        = e_book_backend_file_create_contacts;
2467         sync_class->remove_contacts_sync        = e_book_backend_file_remove_contacts;
2468         sync_class->modify_contacts_sync        = e_book_backend_file_modify_contacts;
2469         sync_class->get_contact_sync            = e_book_backend_file_get_contact;
2470         sync_class->get_contact_list_sync       = e_book_backend_file_get_contact_list;
2471         sync_class->get_contact_list_uids_sync  = e_book_backend_file_get_contact_list_uids;
2472         sync_class->authenticate_user_sync      = e_book_backend_file_authenticate_user;
2473
2474         object_class->dispose = e_book_backend_file_dispose;
2475         object_class->finalize = e_book_backend_file_finalize;
2476
2477 #ifdef G_OS_WIN32
2478         /* Use the gstdio wrappers to open, check, rename and unlink
2479          * files from libdb.
2480          */
2481         db_env_set_func_open (my_open);
2482         db_env_set_func_close (close);
2483         db_env_set_func_exists (my_exists);
2484         db_env_set_func_rename (my_rename);
2485         db_env_set_func_unlink (my_unlink);
2486 #endif
2487 }
2488
2489 static void
2490 e_book_backend_file_init (EBookBackendFile *backend)
2491 {
2492         backend->priv = E_BOOK_BACKEND_FILE_GET_PRIVATE (backend);
2493
2494         g_signal_connect (
2495                 backend, "notify::online",
2496                 G_CALLBACK (e_book_backend_file_notify_online_cb), NULL);
2497 }
2498