Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / addressbook / libedata-book / e-book-backend-summary.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * pas-backend-summary.c
4  * Copyright 2000, 2001, Ximian, Inc.
5  *
6  * Authors:
7  *   Chris Toshok <toshok@ximian.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License, version 2, as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301, USA.
22  */
23
24 #include <config.h>
25
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <utime.h>
31 #include <errno.h>
32
33 #include <glib.h>
34 #include <glib/gstdio.h>
35
36 #include "libedataserver/e-sexp.h"
37 #include "libedataserver/e-data-server-util.h"
38
39 #include "libebook/e-contact.h"
40
41 #include "e-book-backend-summary.h"
42
43 static GObjectClass *parent_class;
44
45 struct _EBookBackendSummaryPrivate {
46         char *summary_path;
47         FILE *fp;
48         guint32 file_version;
49         time_t mtime;
50         gboolean upgraded;
51         gboolean dirty;
52         int flush_timeout_millis;
53         int flush_timeout;
54         GPtrArray *items;
55         GHashTable *id_to_item;
56         guint32 num_items; /* used only for loading */
57 #ifdef SUMMARY_STATS
58         int size;
59 #endif
60 };
61
62 typedef struct {
63         char *id;
64         char *nickname;
65         char *full_name;
66         char *given_name;
67         char *surname;
68         char *file_as;
69         char *email_1;
70         char *email_2;
71         char *email_3;
72         char *email_4;
73         gboolean wants_html;
74         gboolean wants_html_set;
75         gboolean list;
76         gboolean list_show_addresses;
77 } EBookBackendSummaryItem;
78
79 typedef struct {
80         /* these lengths do *not* including the terminating \0, as
81            it's not stored on disk. */
82         guint16 id_len;
83         guint16 nickname_len;
84         guint16 full_name_len; /* version 3.0 field */
85         guint16 given_name_len;
86         guint16 surname_len;
87         guint16 file_as_len;
88         guint16 email_1_len;
89         guint16 email_2_len;
90         guint16 email_3_len;
91         guint16 email_4_len;
92         guint8  wants_html;
93         guint8  wants_html_set;
94         guint8  list;
95         guint8  list_show_addresses;
96 } EBookBackendSummaryDiskItem;
97
98 typedef struct {
99         guint32 file_version;
100         guint32 num_items;
101         guint32 summary_mtime; /* version 2.0 field */
102 } EBookBackendSummaryHeader;
103
104 #define PAS_SUMMARY_MAGIC "PAS-SUMMARY"
105 #define PAS_SUMMARY_MAGIC_LEN 11
106
107 #define PAS_SUMMARY_FILE_VERSION_1_0 1000
108 #define PAS_SUMMARY_FILE_VERSION_2_0 2000
109 #define PAS_SUMMARY_FILE_VERSION_3_0 3000
110 #define PAS_SUMMARY_FILE_VERSION_4_0 4000
111 #define PAS_SUMMARY_FILE_VERSION_5_0 5000
112
113 #define PAS_SUMMARY_FILE_VERSION PAS_SUMMARY_FILE_VERSION_5_0
114
115 static void
116 free_summary_item (EBookBackendSummaryItem *item)
117 {
118         g_free (item->id);
119         g_free (item->nickname);
120         g_free (item->full_name);
121         g_free (item->given_name);
122         g_free (item->surname);
123         g_free (item->file_as);
124         g_free (item->email_1);
125         g_free (item->email_2);
126         g_free (item->email_3);
127         g_free (item->email_4);
128         g_free (item);
129 }
130
131 static void
132 clear_items (EBookBackendSummary *summary)
133 {
134         int i;
135         int num = summary->priv->items->len;
136         for (i = 0; i < num; i++) {
137                 EBookBackendSummaryItem *item = g_ptr_array_remove_index_fast (summary->priv->items, 0);
138                 if (item) {
139                         g_hash_table_remove (summary->priv->id_to_item, item->id);
140                         free_summary_item (item);
141                 }
142         }
143 }
144
145 /**
146  * e_book_backend_summary_new:
147  * @summary_path: a local file system path
148  * @flush_timeout_millis: a flush interval, in milliseconds
149  *
150  * Creates an #EBookBackendSummary object without loading it
151  * or otherwise affecting the file. @flush_timeout_millis
152  * specifies how much time should elapse, at a minimum, from
153  * the summary is changed until it is flushed to disk.
154  *
155  * Return value: A new #EBookBackendSummary.
156  **/
157 EBookBackendSummary*
158 e_book_backend_summary_new (const char *summary_path, int flush_timeout_millis)
159 {
160         EBookBackendSummary *summary = g_object_new (E_TYPE_BACKEND_SUMMARY, NULL);
161
162         summary->priv->summary_path = g_strdup (summary_path);
163         summary->priv->flush_timeout_millis = flush_timeout_millis;
164         summary->priv->file_version = PAS_SUMMARY_FILE_VERSION_4_0;
165
166         return summary;
167 }
168
169 static void
170 e_book_backend_summary_dispose (GObject *object)
171 {
172         EBookBackendSummary *summary = E_BOOK_BACKEND_SUMMARY (object);
173
174         if (summary->priv) {
175                 if (summary->priv->fp)
176                         fclose (summary->priv->fp);
177                 if (summary->priv->dirty)
178                         e_book_backend_summary_save (summary);
179                 else
180                         utime (summary->priv->summary_path, NULL);
181
182                 if (summary->priv->flush_timeout) {
183                         g_source_remove (summary->priv->flush_timeout);
184                         summary->priv->flush_timeout = 0;
185                 }
186
187                 g_free (summary->priv->summary_path);
188                 clear_items (summary);
189                 g_ptr_array_free (summary->priv->items, TRUE);
190
191                 g_hash_table_destroy (summary->priv->id_to_item);
192
193                 g_free (summary->priv);
194                 summary->priv = NULL;
195         }
196
197         if (G_OBJECT_CLASS (parent_class)->dispose)
198                 G_OBJECT_CLASS (parent_class)->dispose (object);
199 }
200
201 static void
202 e_book_backend_summary_class_init (EBookBackendSummaryClass *klass)
203 {
204         GObjectClass  *object_class = G_OBJECT_CLASS (klass);
205
206         parent_class = g_type_class_peek_parent (klass);
207
208         /* Set the virtual methods. */
209
210         object_class->dispose = e_book_backend_summary_dispose;
211 }
212
213 static void
214 e_book_backend_summary_init (EBookBackendSummary *summary)
215 {
216         EBookBackendSummaryPrivate *priv;
217
218         priv             = g_new(EBookBackendSummaryPrivate, 1);
219
220         summary->priv = priv;
221
222         priv->summary_path = NULL;
223         priv->fp = NULL;
224         priv->dirty = FALSE;
225         priv->upgraded = FALSE;
226         priv->items = g_ptr_array_new();
227         priv->id_to_item = g_hash_table_new (g_str_hash, g_str_equal);
228         priv->flush_timeout_millis = 0;
229         priv->flush_timeout = 0;
230 #ifdef SUMMARY_STATS
231         priv->size = 0;
232 #endif
233 }
234
235 /**
236  * e_book_backend_summary_get_type:
237  */
238 GType
239 e_book_backend_summary_get_type (void)
240 {
241         static GType type = 0;
242
243         if (! type) {
244                 GTypeInfo info = {
245                         sizeof (EBookBackendSummaryClass),
246                         NULL, /* base_class_init */
247                         NULL, /* base_class_finalize */
248                         (GClassInitFunc)  e_book_backend_summary_class_init,
249                         NULL, /* class_finalize */
250                         NULL, /* class_data */
251                         sizeof (EBookBackendSummary),
252                         0,    /* n_preallocs */
253                         (GInstanceInitFunc) e_book_backend_summary_init
254                 };
255
256                 type = g_type_register_static (G_TYPE_OBJECT, "EBookBackendSummary", &info, 0);
257         }
258
259         return type;
260 }
261
262 \f
263 static gboolean
264 e_book_backend_summary_check_magic (EBookBackendSummary *summary, FILE *fp)
265 {
266         char buf [PAS_SUMMARY_MAGIC_LEN + 1];
267         int rv;
268
269         memset (buf, 0, sizeof (buf));
270
271         rv = fread (buf, PAS_SUMMARY_MAGIC_LEN, 1, fp);
272         if (rv != 1)
273                 return FALSE;
274         if (strcmp (buf, PAS_SUMMARY_MAGIC))
275                 return FALSE;
276
277         return TRUE;
278 }
279
280 static gboolean
281 e_book_backend_summary_load_header (EBookBackendSummary *summary, FILE *fp,
282                                  EBookBackendSummaryHeader *header)
283 {
284         int rv;
285
286         rv = fread (&header->file_version, sizeof (header->file_version), 1, fp);
287         if (rv != 1)
288                 return FALSE;
289
290         header->file_version = g_ntohl (header->file_version);
291
292         if (header->file_version < PAS_SUMMARY_FILE_VERSION) {
293                 return FALSE; /* this will cause the entire summary to be rebuilt */
294         }
295
296         rv = fread (&header->num_items, sizeof (header->num_items), 1, fp);
297         if (rv != 1)
298                 return FALSE;
299
300         header->num_items = g_ntohl (header->num_items);
301
302         rv = fread (&header->summary_mtime, sizeof (header->summary_mtime), 1, fp);
303         if (rv != 1)
304                 return FALSE;
305         header->summary_mtime = g_ntohl (header->summary_mtime);
306
307         return TRUE;
308 }
309
310 static char *
311 read_string (FILE *fp, int len)
312 {
313         char *buf;
314         int rv;
315
316         buf = g_new0 (char, len + 1);
317
318         rv = fread (buf, len, 1, fp);
319         if (rv != 1) {
320                 g_free (buf);
321                 return NULL;
322         }
323
324         return buf;
325 }
326
327 static gboolean
328 e_book_backend_summary_load_item (EBookBackendSummary *summary,
329                                EBookBackendSummaryItem **new_item)
330 {
331         EBookBackendSummaryItem *item;
332         char *buf;
333         FILE *fp = summary->priv->fp;
334
335         if (summary->priv->file_version >= PAS_SUMMARY_FILE_VERSION_4_0) {
336                 EBookBackendSummaryDiskItem disk_item;
337                 int rv = fread (&disk_item, sizeof (disk_item), 1, fp);
338                 if (rv != 1)
339                         return FALSE;
340
341                 disk_item.id_len = g_ntohs (disk_item.id_len);
342                 disk_item.nickname_len = g_ntohs (disk_item.nickname_len);
343                 disk_item.full_name_len = g_ntohs (disk_item.full_name_len);
344                 disk_item.given_name_len = g_ntohs (disk_item.given_name_len);
345                 disk_item.surname_len = g_ntohs (disk_item.surname_len);
346                 disk_item.file_as_len = g_ntohs (disk_item.file_as_len);
347                 disk_item.email_1_len = g_ntohs (disk_item.email_1_len);
348                 disk_item.email_2_len = g_ntohs (disk_item.email_2_len);
349                 disk_item.email_3_len = g_ntohs (disk_item.email_3_len);
350                 disk_item.email_4_len = g_ntohs (disk_item.email_4_len);
351
352                 item = g_new0 (EBookBackendSummaryItem, 1);
353
354                 item->wants_html = disk_item.wants_html;
355                 item->wants_html_set = disk_item.wants_html_set;
356                 item->list = disk_item.list;
357                 item->list_show_addresses = disk_item.list_show_addresses;
358
359                 if (disk_item.id_len) {
360                         buf = read_string (fp, disk_item.id_len);
361                         if (!buf) {
362                                 free_summary_item (item);
363                                 return FALSE;
364                         }
365                         item->id = buf;
366                 }
367
368                 if (disk_item.nickname_len) {
369                         buf = read_string (fp, disk_item.nickname_len);
370                         if (!buf) {
371                                 free_summary_item (item);
372                                 return FALSE;
373                         }
374                         item->nickname = buf;
375                 }
376
377                 if (disk_item.full_name_len) {
378                         buf = read_string (fp, disk_item.full_name_len);
379                         if (!buf) {
380                                 free_summary_item (item);
381                                 return FALSE;
382                         }
383                         item->full_name = buf;
384                 }
385
386                 if (disk_item.given_name_len) {
387                         buf = read_string (fp, disk_item.given_name_len);
388                         if (!buf) {
389                                 free_summary_item (item);
390                                 return FALSE;
391                         }
392                         item->given_name = buf;
393                 }
394
395                 if (disk_item.surname_len) {
396                         buf = read_string (fp, disk_item.surname_len);
397                         if (!buf) {
398                                 free_summary_item (item);
399                                 return FALSE;
400                         }
401                         item->surname = buf;
402                 }
403
404                 if (disk_item.file_as_len) {
405                         buf = read_string (fp, disk_item.file_as_len);
406                         if (!buf) {
407                                 free_summary_item (item);
408                                 return FALSE;
409                         }
410                         item->file_as = buf;
411                 }
412
413                 if (disk_item.email_1_len) {
414                         buf = read_string (fp, disk_item.email_1_len);
415                         if (!buf) {
416                                 free_summary_item (item);
417                                 return FALSE;
418                         }
419                         item->email_1 = buf;
420                 }
421
422                 if (disk_item.email_2_len) {
423                         buf = read_string (fp, disk_item.email_2_len);
424                         if (!buf) {
425                                 free_summary_item (item);
426                                 return FALSE;
427                         }
428                         item->email_2 = buf;
429                 }
430
431                 if (disk_item.email_3_len) {
432                         buf = read_string (fp, disk_item.email_3_len);
433                         if (!buf) {
434                                 free_summary_item (item);
435                                 return FALSE;
436                         }
437                         item->email_3 = buf;
438                 }
439
440                 if (disk_item.email_4_len) {
441                         buf = read_string (fp, disk_item.email_4_len);
442                         if (!buf) {
443                                 free_summary_item (item);
444                                 return FALSE;
445                         }
446                         item->email_4 = buf;
447                 }
448
449                 /* the only field that has to be there is the id */
450                 if (!item->id) {
451                         free_summary_item (item);
452                         return FALSE;
453                 }
454         }
455         else {
456                 /* unhandled file version */
457                 return FALSE;
458         }
459
460         *new_item = item;
461         return TRUE;
462 }
463
464 /* opens the file and loads the header */
465 static gboolean
466 e_book_backend_summary_open (EBookBackendSummary *summary)
467 {
468         FILE *fp;
469         EBookBackendSummaryHeader header;
470         struct stat sb;
471
472         if (summary->priv->fp)
473                 return TRUE;
474
475         if (g_stat (summary->priv->summary_path, &sb) == -1) {
476                 /* if there's no summary present, look for the .new
477                    file and rename it if it's there, and attempt to
478                    load that */
479                 char *new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
480                 if (g_stat (new_filename, &sb) == -1) {
481                         g_free (new_filename);
482                         return FALSE;
483                 }
484                 else {
485                         g_rename (new_filename, summary->priv->summary_path);
486                         g_free (new_filename);
487                 }
488         }
489
490         fp = g_fopen (summary->priv->summary_path, "rb");
491         if (!fp) {
492                 g_warning ("failed to open summary file");
493                 return FALSE;
494         }
495
496         if (!e_book_backend_summary_check_magic (summary, fp)) {
497                 g_warning ("file is not a valid summary file");
498                 fclose (fp);
499                 return FALSE;
500         }
501
502         if (!e_book_backend_summary_load_header (summary, fp, &header)) {
503                 g_warning ("failed to read summary header");
504                 fclose (fp);
505                 return FALSE;
506         }
507
508         summary->priv->num_items = header.num_items;
509         summary->priv->file_version = header.file_version;
510         summary->priv->mtime = sb.st_mtime;
511         summary->priv->fp = fp;
512
513         return TRUE;
514 }
515
516 /**
517  * e_book_backend_summary_load:
518  * @summary: an #EBookBackendSummary
519  *
520  * Attempts to load @summary from disk. The load is successful if
521  * the file was located, it was in the correct format, and it was
522  * not out of date.
523  *
524  * Return value: %TRUE if the load succeeded, %FALSE if it failed.
525  **/
526 gboolean
527 e_book_backend_summary_load (EBookBackendSummary *summary)
528 {
529         EBookBackendSummaryItem *new_item;
530         int i;
531         
532         clear_items (summary);
533
534         if (!e_book_backend_summary_open (summary))
535                 return FALSE;
536
537         for (i = 0; i < summary->priv->num_items; i ++) {
538                 if (!e_book_backend_summary_load_item (summary, &new_item)) {
539                         g_warning ("error while reading summary item");
540                         clear_items (summary);
541                         fclose (summary->priv->fp);
542                         summary->priv->fp = NULL;
543                         summary->priv->dirty = FALSE;
544                         return FALSE;
545                 }
546
547                 g_ptr_array_add (summary->priv->items, new_item);
548                 g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);
549         }
550
551         if (summary->priv->upgraded) {
552                 e_book_backend_summary_save (summary);
553         }
554         summary->priv->dirty = FALSE;
555
556         return TRUE;
557 }
558
559 static gboolean
560 e_book_backend_summary_save_magic (FILE *fp)
561 {
562         int rv;
563         rv = fwrite (PAS_SUMMARY_MAGIC, PAS_SUMMARY_MAGIC_LEN, 1, fp);
564         if (rv != 1)
565                 return FALSE;
566
567         return TRUE;
568 }
569
570 static gboolean
571 e_book_backend_summary_save_header (EBookBackendSummary *summary, FILE *fp)
572 {
573         EBookBackendSummaryHeader header;
574         int rv;
575
576         header.file_version = g_htonl (PAS_SUMMARY_FILE_VERSION);
577         header.num_items = g_htonl (summary->priv->items->len);
578         header.summary_mtime = g_htonl (time (NULL));
579
580         rv = fwrite (&header, sizeof (header), 1, fp);
581         if (rv != 1)
582                 return FALSE;
583
584         return TRUE;
585 }
586
587 static gboolean
588 save_string (const char *str, FILE *fp)
589 {
590         int rv;
591
592         if (!str || !*str)
593                 return TRUE;
594
595         rv = fwrite (str, strlen (str), 1, fp);
596         return (rv == 1);
597 }
598
599 static gboolean
600 e_book_backend_summary_save_item (EBookBackendSummary *summary, FILE *fp, EBookBackendSummaryItem *item)
601 {
602         EBookBackendSummaryDiskItem disk_item;
603         int len;
604         int rv;
605
606         len = item->id ? strlen (item->id) : 0;
607         disk_item.id_len = g_htons (len);
608
609         len = item->nickname ? strlen (item->nickname) : 0;
610         disk_item.nickname_len = g_htons (len);
611
612         len = item->given_name ? strlen (item->given_name) : 0;
613         disk_item.given_name_len = g_htons (len);
614
615         len = item->full_name ? strlen (item->full_name) : 0;
616         disk_item.full_name_len = g_htons (len);
617
618         len = item->surname ? strlen (item->surname) : 0;
619         disk_item.surname_len = g_htons (len);
620
621         len = item->file_as ? strlen (item->file_as) : 0;
622         disk_item.file_as_len = g_htons (len);
623
624         len = item->email_1 ? strlen (item->email_1) : 0;
625         disk_item.email_1_len = g_htons (len);
626
627         len = item->email_2 ? strlen (item->email_2) : 0;
628         disk_item.email_2_len = g_htons (len);
629
630         len = item->email_3 ? strlen (item->email_3) : 0;
631         disk_item.email_3_len = g_htons (len);
632
633         len = item->email_4 ? strlen (item->email_4) : 0;
634         disk_item.email_4_len = g_htons (len);
635
636         disk_item.wants_html = item->wants_html;
637         disk_item.wants_html_set = item->wants_html_set;
638         disk_item.list = item->list;
639         disk_item.list_show_addresses = item->list_show_addresses;
640
641         rv = fwrite (&disk_item, sizeof(disk_item), 1, fp);
642         if (rv != 1)
643                 return FALSE;
644
645         if (!save_string (item->id, fp))
646                 return FALSE;
647         if (!save_string (item->nickname, fp))
648                 return FALSE;
649         if (!save_string (item->full_name, fp))
650                 return FALSE;
651         if (!save_string (item->given_name, fp))
652                 return FALSE;
653         if (!save_string (item->surname, fp))
654                 return FALSE;
655         if (!save_string (item->file_as, fp))
656                 return FALSE;
657         if (!save_string (item->email_1, fp))
658                 return FALSE;
659         if (!save_string (item->email_2, fp))
660                 return FALSE;
661         if (!save_string (item->email_3, fp))
662                 return FALSE;
663         if (!save_string (item->email_4, fp))
664                 return FALSE;
665
666         return TRUE;
667 }
668
669 /**
670  * e_book_backend_summary_save:
671  * @summary: an #EBookBackendSummary
672  *
673  * Attempts to save @summary to disk.
674  *
675  * Return value: %TRUE if the save succeeded, %FALSE otherwise.
676  **/
677 gboolean
678 e_book_backend_summary_save (EBookBackendSummary *summary)
679 {
680         struct stat sb;
681         FILE *fp = NULL;
682         char *new_filename = NULL;
683         int i;
684
685         if (!summary->priv->dirty)
686                 return TRUE;
687
688         new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
689
690         fp = g_fopen (new_filename, "wb");
691         if (!fp) {
692                 g_warning ("could not create new summary file");
693                 goto lose;
694         }
695
696         if (!e_book_backend_summary_save_magic (fp)) {
697                 g_warning ("could not write magic to new summary file");
698                 goto lose;
699         }
700
701         if (!e_book_backend_summary_save_header (summary, fp)) {
702                 g_warning ("could not write header to new summary file");
703                 goto lose;
704         }
705
706         for (i = 0; i < summary->priv->items->len; i ++) {
707                 EBookBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
708                 if (!e_book_backend_summary_save_item (summary, fp, item)) {
709                         g_warning ("failed to write an item to new summary file, errno = %d", errno);
710                         goto lose;
711                 }
712         }
713
714         fclose (fp);
715
716         /* if we have a queued flush, clear it (since we just flushed) */
717         if (summary->priv->flush_timeout) {
718                 g_source_remove (summary->priv->flush_timeout);
719                 summary->priv->flush_timeout = 0;
720         }
721
722         /* unlink the old summary and rename the new one */
723         g_unlink (summary->priv->summary_path);
724         g_rename (new_filename, summary->priv->summary_path);
725
726         g_free (new_filename);
727
728         /* lastly, update the in memory mtime to that of the file */
729         if (g_stat (summary->priv->summary_path, &sb) == -1) {
730                 g_warning ("error stat'ing saved summary");
731         }
732         else {
733                 summary->priv->mtime = sb.st_mtime;
734         }
735
736         summary->priv->dirty = FALSE;
737         return TRUE;
738
739  lose:
740         if (fp)
741                 fclose (fp);
742         if (new_filename)
743                 g_unlink (new_filename);
744         g_free (new_filename);
745         return FALSE;
746 }
747
748 /**
749  * e_book_backend_summary_add_contact:
750  * @summary: an #EBookBackendSummary
751  * @contact: an #EContact to add
752  *
753  * Adds a summary of @contact to @summary. Does not check if
754  * the contact already has a summary.
755  **/
756 void
757 e_book_backend_summary_add_contact (EBookBackendSummary *summary, EContact *contact)
758 {
759         EBookBackendSummaryItem *new_item;
760         char *id = NULL;
761
762         /* ID normally should not be NULL for a contact. */
763         /* Added this check as groupwise server sometimes returns
764          * contacts with NULL id 
765          */
766         id = e_contact_get (contact, E_CONTACT_UID);
767         if (!id) {
768                 g_warning ("found a contact with NULL uid");
769                 return;
770         }
771
772         new_item = g_new0 (EBookBackendSummaryItem, 1);
773
774         new_item->id         = id;
775         new_item->nickname   = e_contact_get (contact, E_CONTACT_NICKNAME);
776         new_item->full_name  = e_contact_get (contact, E_CONTACT_FULL_NAME);
777         new_item->given_name = e_contact_get (contact, E_CONTACT_GIVEN_NAME);
778         new_item->surname    = e_contact_get (contact, E_CONTACT_FAMILY_NAME);
779         new_item->file_as    = e_contact_get (contact, E_CONTACT_FILE_AS);
780         new_item->email_1    = e_contact_get (contact, E_CONTACT_EMAIL_1);
781         new_item->email_2    = e_contact_get (contact, E_CONTACT_EMAIL_2);
782         new_item->email_3    = e_contact_get (contact, E_CONTACT_EMAIL_3);
783         new_item->email_4    = e_contact_get (contact, E_CONTACT_EMAIL_4);
784         new_item->list       = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST));
785         new_item->list_show_addresses = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_LIST_SHOW_ADDRESSES));
786         new_item->wants_html = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_WANTS_HTML));
787
788         g_ptr_array_add (summary->priv->items, new_item);
789         g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);
790
791 #ifdef SUMMARY_STATS
792         summary->priv->size += sizeof (EBookBackendSummaryItem);
793         summary->priv->size += new_item->id ? strlen (new_item->id) : 0;
794         summary->priv->size += new_item->nickname ? strlen (new_item->nickname) : 0;
795         summary->priv->size += new_item->full_name ? strlen (new_item->full_name) : 0;
796         summary->priv->size += new_item->given_name ? strlen (new_item->given_name) : 0;
797         summary->priv->size += new_item->surname ? strlen (new_item->surname) : 0;
798         summary->priv->size += new_item->file_as ? strlen (new_item->file_as) : 0;
799         summary->priv->size += new_item->email_1 ? strlen (new_item->email_1) : 0;
800         summary->priv->size += new_item->email_2 ? strlen (new_item->email_2) : 0;
801         summary->priv->size += new_item->email_3 ? strlen (new_item->email_3) : 0;
802         summary->priv->size += new_item->email_4 ? strlen (new_item->email_4) : 0;
803 #endif
804         e_book_backend_summary_touch (summary);
805 }
806
807 /**
808  * e_book_backend_summary_remove_contact:
809  * @summary: an #EBookBackendSummary
810  * @id: a unique contact ID string
811  *
812  * Removes the summary of the contact identified by @id from @summary.
813  **/
814 void
815 e_book_backend_summary_remove_contact (EBookBackendSummary *summary, const char *id)
816 {
817         EBookBackendSummaryItem *item = g_hash_table_lookup (summary->priv->id_to_item, id);
818
819         if (item) {
820                 g_ptr_array_remove (summary->priv->items, item);
821                 g_hash_table_remove (summary->priv->id_to_item, id);
822                 free_summary_item (item);
823                 e_book_backend_summary_touch (summary);
824                 return;
825         }
826
827         g_warning ("e_book_backend_summary_remove_contact: unable to locate id `%s'", id);
828 }
829
830 /**
831  * e_book_backend_summary_check_contact:
832  * @summary: an #EBookBackendSummary
833  * @id: a unique contact ID string
834  *
835  * Checks if a summary of the contact identified by @id
836  * exists in @summary.
837  *
838  * Return value: %TRUE if the summary exists, %FALSE otherwise.
839  **/
840 gboolean
841 e_book_backend_summary_check_contact (EBookBackendSummary *summary, const char *id)
842 {
843         return g_hash_table_lookup (summary->priv->id_to_item, id) != NULL;
844 }
845
846 static gboolean
847 summary_flush_func (gpointer data)
848 {
849         EBookBackendSummary *summary = E_BOOK_BACKEND_SUMMARY (data);
850
851         if (!summary->priv->dirty) {
852                 summary->priv->flush_timeout = 0;
853                 return FALSE;
854         }
855
856         if (!e_book_backend_summary_save (summary)) {
857                 /* this isn't fatal, as we can just either 1) flush
858                    out with the next change, or 2) regen the summary
859                    when we next load the uri */
860                 g_warning ("failed to flush summary file to disk");
861                 return TRUE; /* try again after the next timeout */
862         }
863         
864         g_message ("Flushed summary to disk");
865         
866         /* we only want this to execute once, so return FALSE and set
867            summary->flush_timeout to 0 */
868         summary->priv->flush_timeout = 0;
869         return FALSE;
870 }
871
872 /**
873  * e_book_backend_summary_touch:
874  * @summary: an #EBookBackendSummary
875  *
876  * Indicates that @summary has changed and should be flushed to disk.
877  **/
878 void
879 e_book_backend_summary_touch (EBookBackendSummary *summary)
880 {
881         summary->priv->dirty = TRUE;
882         if (!summary->priv->flush_timeout
883             && summary->priv->flush_timeout_millis)
884                 summary->priv->flush_timeout = g_timeout_add (summary->priv->flush_timeout_millis,
885                                                               summary_flush_func, summary);
886 }
887
888 /**
889  * e_book_backend_summary_is_up_to_date:
890  * @summary: an #EBookBackendSummary
891  * @t: the time to compare with
892  *
893  * Checks if @summary is more recent than @t.
894  *
895  * Return value: %TRUE if the summary is up to date, %FALSE otherwise.
896  **/
897 gboolean
898 e_book_backend_summary_is_up_to_date (EBookBackendSummary *summary, time_t t)
899 {
900         if (!e_book_backend_summary_open (summary))
901                 return FALSE;
902         else
903                 return summary->priv->mtime >= t;
904 }
905
906 \f
907 /* we only want to do summary queries if the query is over the set fields in the summary */
908
909 static ESExpResult *
910 func_check(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
911 {
912         ESExpResult *r;
913         int truth = FALSE;
914
915         if (argc == 2
916             && argv[0]->type == ESEXP_RES_STRING
917             && argv[1]->type == ESEXP_RES_STRING) {
918                 char *query_name = argv[0]->value.string;
919
920                 if (!strcmp (query_name, "nickname") ||
921                     !strcmp (query_name, "full_name") ||
922                     !strcmp (query_name, "file_as") ||
923                     !strcmp (query_name, "email")) {
924                         truth = TRUE;
925                 }
926         }
927
928         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
929         r->value.bool = truth;
930         
931         return r;
932 }
933
934 /* 'builtin' functions */
935 static struct {
936         char *name;
937         ESExpFunc *func;
938         int type;               /* set to 1 if a function can perform shortcut evaluation, or
939                                    doesn't execute everything, 0 otherwise */
940 } check_symbols[] = {
941         { "contains", func_check, 0 },
942         { "is", func_check, 0 },
943         { "beginswith", func_check, 0 },
944         { "endswith", func_check, 0 },
945         { "exists", func_check, 0 }
946 };
947
948 /**
949  * e_book_backend_summary_is_summary_query:
950  * @summary: an #EBookBackendSummary
951  * @query: an s-expression to check
952  *
953  * Checks if @query can be satisfied by searching only the fields
954  * stored by @summary.
955  *
956  * Return value: %TRUE if the query can be satisfied, %FALSE otherwise.
957  **/
958 gboolean
959 e_book_backend_summary_is_summary_query (EBookBackendSummary *summary, const char *query)
960 {
961         ESExp *sexp;
962         ESExpResult *r;
963         gboolean retval;
964         int i;
965         int esexp_error;
966
967         sexp = e_sexp_new();
968
969         for(i=0;i<sizeof(check_symbols)/sizeof(check_symbols[0]);i++) {
970                 if (check_symbols[i].type == 1) {
971                         e_sexp_add_ifunction(sexp, 0, check_symbols[i].name,
972                                              (ESExpIFunc *)check_symbols[i].func, summary);
973                 } else {
974                         e_sexp_add_function(sexp, 0, check_symbols[i].name,
975                                             check_symbols[i].func, summary);
976                 }
977         }
978
979         e_sexp_input_text(sexp, query, strlen(query));
980         esexp_error = e_sexp_parse(sexp);
981
982         if (esexp_error == -1) {
983                 return FALSE;
984         }
985
986         r = e_sexp_eval(sexp);
987
988         retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);
989
990         e_sexp_result_free(sexp, r);
991
992         e_sexp_unref (sexp);
993
994         return retval;
995 }
996
997 \f
998
999 /* the actual query mechanics */
1000 static ESExpResult *
1001 do_compare (EBookBackendSummary *summary, struct _ESExp *f, int argc,
1002             struct _ESExpResult **argv,
1003             char *(*compare)(const char*, const char*))
1004 {
1005         GPtrArray *result = g_ptr_array_new ();
1006         ESExpResult *r;
1007         int i;
1008
1009         if (argc == 2
1010             && argv[0]->type == ESEXP_RES_STRING
1011             && argv[1]->type == ESEXP_RES_STRING) {
1012
1013                 for (i = 0; i < summary->priv->items->len; i ++) {
1014                         EBookBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
1015                         if (!strcmp (argv[0]->value.string, "full_name")) {
1016                                 char *given = item->given_name;
1017                                 char *surname = item->surname;
1018                                 char *full_name = item->full_name;
1019
1020                                 if ((given && compare (given, argv[1]->value.string))
1021                                     || (surname && compare (surname, argv[1]->value.string))
1022                                     || (full_name && compare (full_name, argv[1]->value.string)))
1023                                         g_ptr_array_add (result, item->id);
1024                         }
1025                         else if (!strcmp (argv[0]->value.string, "email")) {
1026                                 char *email_1 = item->email_1;
1027                                 char *email_2 = item->email_2;
1028                                 char *email_3 = item->email_3;
1029                                 char *email_4 = item->email_4;
1030                                 if ((email_1 && compare (email_1, argv[1]->value.string))
1031                                     || (email_2 && compare (email_2, argv[1]->value.string))
1032                                     || (email_3 && compare (email_3, argv[1]->value.string))
1033                                     || (email_4 && compare (email_4, argv[1]->value.string)))
1034                                         g_ptr_array_add (result, item->id);
1035                         }
1036                         else if (!strcmp (argv[0]->value.string, "file_as")) {
1037                                 char *file_as = item->file_as;
1038                                 if (file_as && compare (file_as, argv[1]->value.string))
1039                                         g_ptr_array_add (result, item->id);
1040                         }
1041                         else if (!strcmp (argv[0]->value.string, "nickname")) {
1042                                 char *nickname = item->nickname;
1043                                 if (nickname && compare (nickname, argv[1]->value.string))
1044                                         g_ptr_array_add (result, item->id);
1045                         }
1046                 }
1047         }
1048
1049         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1050         r->value.ptrarray = result;
1051
1052         return r;
1053 }
1054
1055 static ESExpResult *
1056 func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
1057 {
1058         EBookBackendSummary *summary = data;
1059
1060         return do_compare (summary, f, argc, argv, (char *(*)(const char*, const char*)) e_util_utf8_strstrcase);
1061 }
1062
1063 static char *
1064 is_helper (const char *s1, const char *s2)
1065 {
1066         if (!e_util_utf8_strcasecmp(s1, s2))
1067                 return (char*)s1;
1068         else
1069                 return NULL;
1070 }
1071
1072 static ESExpResult *
1073 func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
1074 {
1075         EBookBackendSummary *summary = data;
1076
1077         return do_compare (summary, f, argc, argv, is_helper);
1078 }
1079
1080 static char *
1081 endswith_helper (const char *s1, const char *s2)
1082 {
1083         char *p;
1084         if ((p = (char*)e_util_utf8_strstrcase(s1, s2))
1085             && (strlen(p) == strlen(s2)))
1086                 return p;
1087         else
1088                 return NULL;
1089 }
1090
1091 static ESExpResult *
1092 func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
1093 {
1094         EBookBackendSummary *summary = data;
1095
1096         return do_compare (summary, f, argc, argv, endswith_helper);
1097 }
1098
1099 static char *
1100 beginswith_helper (const char *s1, const char *s2)
1101 {
1102         char *p;
1103         if ((p = (char*)e_util_utf8_strstrcase(s1, s2))
1104             && (p == s1))
1105                 return p;
1106         else
1107                 return NULL;
1108 }
1109
1110 static ESExpResult *
1111 func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
1112 {
1113         EBookBackendSummary *summary = data;
1114
1115         return do_compare (summary, f, argc, argv, beginswith_helper);
1116 }
1117
1118 /* 'builtin' functions */
1119 static struct {
1120         char *name;
1121         ESExpFunc *func;
1122         int type;               /* set to 1 if a function can perform shortcut evaluation, or
1123                                    doesn't execute everything, 0 otherwise */
1124 } symbols[] = {
1125         { "contains", func_contains, 0 },
1126         { "is", func_is, 0 },
1127         { "beginswith", func_beginswith, 0 },
1128         { "endswith", func_endswith, 0 },
1129 };
1130
1131 /**
1132  * e_book_backend_summary_search:
1133  * @summary: an #EBookBackendSummary
1134  * @query: an s-expression
1135  *
1136  * Searches @summary for contacts matching @query.
1137  *
1138  * Return value: A #GPtrArray of pointers to contact ID strings.
1139  **/
1140 GPtrArray*
1141 e_book_backend_summary_search (EBookBackendSummary *summary, const char *query)
1142 {
1143         ESExp *sexp;
1144         ESExpResult *r;
1145         GPtrArray *retval = g_ptr_array_new();
1146         int i;
1147         int esexp_error;
1148
1149         sexp = e_sexp_new();
1150
1151         for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
1152                 if (symbols[i].type == 1) {
1153                         e_sexp_add_ifunction(sexp, 0, symbols[i].name,
1154                                              (ESExpIFunc *)symbols[i].func, summary);
1155                 } else {
1156                         e_sexp_add_function(sexp, 0, symbols[i].name,
1157                                             symbols[i].func, summary);
1158                 }
1159         }
1160
1161         e_sexp_input_text(sexp, query, strlen(query));
1162         esexp_error = e_sexp_parse(sexp);
1163
1164         if (esexp_error == -1) {
1165                 return NULL;
1166         }
1167
1168         r = e_sexp_eval(sexp);
1169
1170         if (r && r->type == ESEXP_RES_ARRAY_PTR && r->value.ptrarray) {
1171                 GPtrArray *ptrarray = r->value.ptrarray;
1172                 int i;
1173
1174                 for (i = 0; i < ptrarray->len; i ++)
1175                         g_ptr_array_add (retval, g_ptr_array_index (ptrarray, i));
1176         }
1177
1178         e_sexp_result_free(sexp, r);
1179
1180         e_sexp_unref (sexp);
1181
1182         return retval;
1183 }
1184
1185 /**
1186  * e_book_backend_summary_get_summary_vcard:
1187  * @summary: an #EBookBackendSummary
1188  * @id: a unique contact ID
1189  *
1190  * Constructs and returns a VCard from the contact summary specified
1191  * by @id.
1192  *
1193  * Return value: A new VCard, or %NULL if the contact summary didn't exist.
1194  **/
1195 char*
1196 e_book_backend_summary_get_summary_vcard(EBookBackendSummary *summary, const char *id)
1197 {
1198         EBookBackendSummaryItem *item = g_hash_table_lookup (summary->priv->id_to_item, id);
1199
1200         if (item) {
1201                 EContact *contact = e_contact_new ();
1202                 char *vcard;
1203
1204                 e_contact_set (contact, E_CONTACT_UID, item->id);
1205                 e_contact_set (contact, E_CONTACT_FILE_AS, item->file_as);
1206                 e_contact_set (contact, E_CONTACT_GIVEN_NAME, item->given_name);
1207                 e_contact_set (contact, E_CONTACT_FAMILY_NAME, item->surname);
1208                 e_contact_set (contact, E_CONTACT_NICKNAME, item->nickname);
1209                 e_contact_set (contact, E_CONTACT_FULL_NAME, item->full_name);
1210                 e_contact_set (contact, E_CONTACT_EMAIL_1, item->email_1);
1211                 e_contact_set (contact, E_CONTACT_EMAIL_2, item->email_2);
1212                 e_contact_set (contact, E_CONTACT_EMAIL_3, item->email_3);
1213                 e_contact_set (contact, E_CONTACT_EMAIL_4, item->email_4);
1214
1215                 e_contact_set (contact, E_CONTACT_IS_LIST, GINT_TO_POINTER (item->list));
1216                 e_contact_set (contact, E_CONTACT_LIST_SHOW_ADDRESSES, GINT_TO_POINTER (item->list_show_addresses));
1217                 e_contact_set (contact, E_CONTACT_WANTS_HTML, GINT_TO_POINTER (item->wants_html));
1218
1219                 vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1220
1221                 g_object_unref (contact);
1222
1223                 return vcard;
1224         }
1225         else {
1226                 g_warning ("in unable to locate card `%s' in summary", id);
1227                 return NULL;
1228         }
1229 }
1230