1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2013 Intel Corporation
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Tristan Van Berkom <tristanvb@openismus.com>
18  */
19 
20 /**
21  * SECTION: e-book-cache
22  * @include: libedata-book/libedata-book.h
23  * @short_description: An #ECache descendant for addressbooks
24  *
25  * The #EBookCache is an API for storing and looking up #EContact(s)
26  * in an #ECache. It also supports cursors.
27  *
28  * The API is thread safe, in the similar way as the #ECache is.
29  *
30  * Any operations which can take a lot of time to complete (depending
31  * on the size of your addressbook) can be cancelled using a #GCancellable.
32  *
33  * Depending on your summary configuration, your mileage will vary. Refer
34  * to the #ESourceBackendSummarySetup for configuring your addressbook
35  * for the type of usage you mean to make of it.
36  **/
37 
38 #include "evolution-data-server-config.h"
39 
40 #include <locale.h>
41 #include <string.h>
42 #include <errno.h>
43 #include <sqlite3.h>
44 
45 #include <glib/gi18n-lib.h>
46 #include <glib/gstdio.h>
47 
48 #include "e-book-backend-sexp.h"
49 
50 #include "e-book-cache.h"
51 
52 #define E_BOOK_CACHE_VERSION		2
53 #define INSERT_MULTI_STMT_BYTES		128
54 #define COLUMN_DEFINITION_BYTES		32
55 #define GENERATED_QUERY_BYTES		1024
56 
57 /* We use a 64 bitmask to track which auxiliary tables
58  * are needed to satisfy a query, it's doubtful that
59  * anyone will need an addressbook with 64 fields configured
60  * in the summary.
61  */
62 #define EBC_MAX_SUMMARY_FIELDS      64
63 
64 /* The number of SQLite virtual machine instructions that are
65  * evaluated at a time, the user passed GCancellable is
66  * checked between each batch of evaluated instructions.
67  */
68 #define EBC_CANCEL_BATCH_SIZE       200
69 
70 #define EBC_ESCAPE_SEQUENCE        "ESCAPE '^'"
71 
72 /* Names for custom functions */
73 #define EBC_FUNC_COMPARE_VCARD     "compare_vcard"
74 #define EBC_FUNC_EQPHONE_EXACT     "eqphone_exact"
75 #define EBC_FUNC_EQPHONE_NATIONAL  "eqphone_national"
76 #define EBC_FUNC_EQPHONE_SHORT     "eqphone_short"
77 
78 /* Fallback collations are generated as with a prefix and an EContactField name */
79 #define EBC_COLLATE_PREFIX         "book_cache_"
80 
81 /* A special vcard attribute that we use only for private vcards */
82 #define EBC_VCARD_SORT_KEY         "X-EVOLUTION-SORT-KEY"
83 
84 /* Key names for e_cache_dup/set_key{_int} functions */
85 #define EBC_KEY_MULTIVALUES	"multivalues"
86 #define EBC_KEY_LC_COLLATE	"lc_collate"
87 #define EBC_KEY_COUNTRYCODE	"countrycode"
88 
89 /* Suffixes for column names used to store specialized data */
90 #define EBC_SUFFIX_REVERSE         "reverse"
91 #define EBC_SUFFIX_SORT_KEY        "localized"
92 #define EBC_SUFFIX_PHONE           "phone"
93 #define EBC_SUFFIX_COUNTRY         "country"
94 
95 /* Track EBookIndexType's in a bit mask  */
96 #define INDEX_FLAG(type)  (1 << E_BOOK_INDEX_##type)
97 
98 #define EBC_COLUMN_EXTRA	"bdata"
99 #define EBC_COLUMN_CUSTOM_FLAGS	"custom_flags"
100 
101 typedef struct {
102 	EContactField field_id;		/* The EContact field */
103 	GType type;			/* The GType (only support string or gboolean) */
104 	const gchar *dbname;		/* The key for this field in the sqlite3 table */
105 	gint index;			/* Types of searches this field should support (see EBookIndexType) */
106 	gchar *dbname_idx_suffix;	/* dbnames for various indexes; can be NULL */
107 	gchar *dbname_idx_phone;
108 	gchar *dbname_idx_country;
109 	gchar *dbname_idx_sort_key;
110 	gchar *aux_table;		/* Name of auxiliary table for this field, for multivalued fields only */
111 	gchar *aux_table_symbolic;	/* Symbolic name of auxiliary table used in queries */
112 } SummaryField;
113 
114 struct _EBookCachePrivate {
115 	ESource *source;		/* Optional, can be %NULL */
116 
117 	/* Parameters and settings */
118 	gchar *locale;			/* The current locale */
119 	gchar *region_code;		/* Region code (for phone number parsing) */
120 
121 	/* Summary configuration */
122 	SummaryField *summary_fields;
123 	gint n_summary_fields;
124 
125 	ECollator *collator;		/* The ECollator to create sort keys for any sortable fields */
126 };
127 
128 enum {
129 	PROP_0,
130 	PROP_LOCALE
131 };
132 
133 enum {
134 	E164_CHANGED,
135 	DUP_CONTACT_REVISION,
136 	LAST_SIGNAL
137 };
138 
139 static guint signals[LAST_SIGNAL];
140 
141 static EBookCacheCursor *e_book_cache_cursor_fake_ref (EBookCacheCursor *cursor);
142 static void e_book_cache_cursor_fake_unref (EBookCacheCursor *cursor);
143 
G_DEFINE_TYPE_WITH_CODE(EBookCache,e_book_cache,E_TYPE_CACHE,G_ADD_PRIVATE (EBookCache)G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))144 G_DEFINE_TYPE_WITH_CODE (EBookCache, e_book_cache, E_TYPE_CACHE,
145 			 G_ADD_PRIVATE (EBookCache)
146 			 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
147 
148 G_DEFINE_BOXED_TYPE (EBookCacheSearchData, e_book_cache_search_data, e_book_cache_search_data_copy, e_book_cache_search_data_free)
149 G_DEFINE_BOXED_TYPE (EBookCacheCursor, e_book_cache_cursor, e_book_cache_cursor_fake_ref, e_book_cache_cursor_fake_unref)
150 
151 /**
152  * e_book_cache_search_data_new:
153  * @uid: a contact UID; cannot be %NULL
154  * @vcard: the contact as a vCard string; cannot be %NULL
155  * @extra: (nullable): any extra data stored with the contact, or %NULL
156  *
157  * Creates a new EBookCacheSearchData prefilled with the given values.
158  *
159  * Returns: (transfer full): A new #EBookCacheSearchData. Free it with
160  *    e_book_cache_search_data_free() when no longer needed.
161  *
162  * Since: 3.26
163  **/
164 EBookCacheSearchData *
165 e_book_cache_search_data_new (const gchar *uid,
166 			      const gchar *vcard,
167 			      const gchar *extra)
168 {
169 	EBookCacheSearchData *data;
170 
171 	g_return_val_if_fail (uid != NULL, NULL);
172 	g_return_val_if_fail (vcard != NULL, NULL);
173 
174 	data = g_slice_new0 (EBookCacheSearchData);
175 	data->uid = g_strdup (uid);
176 	data->vcard = g_strdup (vcard);
177 	data->extra = g_strdup (extra);
178 
179 	return data;
180 }
181 
182 /**
183  * e_book_cache_search_data_copy:
184  * @data: (nullable): a source #EBookCacheSearchData to copy, or %NULL
185  *
186  * Returns: (transfer full): Copy of the given @data. Free it with
187  *    e_book_cache_search_data_free() when no longer needed.
188  *    If the @data is %NULL, then returns %NULL as well.
189  *
190  * Since: 3.26
191  **/
192 EBookCacheSearchData *
e_book_cache_search_data_copy(const EBookCacheSearchData * data)193 e_book_cache_search_data_copy (const EBookCacheSearchData *data)
194 {
195 	if (!data)
196 		return NULL;
197 
198 	return e_book_cache_search_data_new (data->uid, data->vcard, data->extra);
199 }
200 
201 /**
202  * e_book_cache_search_data_free:
203  * @data: (nullable): an #EBookCacheSearchData
204  *
205  * Frees the @data structure, previously allocated with e_book_cache_search_data_new()
206  * or e_book_cache_search_data_copy().
207  *
208  * Since: 3.26
209  **/
210 void
e_book_cache_search_data_free(gpointer ptr)211 e_book_cache_search_data_free (gpointer ptr)
212 {
213 	EBookCacheSearchData *data = ptr;
214 
215 	if (data) {
216 		g_free (data->uid);
217 		g_free (data->vcard);
218 		g_free (data->extra);
219 		g_slice_free (EBookCacheSearchData, data);
220 	}
221 }
222 
223 /* Default summary configuration */
224 static EContactField default_summary_fields[] = {
225 	E_CONTACT_UID,
226 	E_CONTACT_REV,
227 	E_CONTACT_FILE_AS,
228 	E_CONTACT_NICKNAME,
229 	E_CONTACT_FULL_NAME,
230 	E_CONTACT_GIVEN_NAME,
231 	E_CONTACT_FAMILY_NAME,
232 	E_CONTACT_EMAIL,
233 	E_CONTACT_TEL,
234 	E_CONTACT_IS_LIST,
235 	E_CONTACT_LIST_SHOW_ADDRESSES,
236 	E_CONTACT_WANTS_HTML,
237 	E_CONTACT_X509_CERT,
238 	E_CONTACT_PGP_CERT
239 };
240 
241 /* Create indexes on full_name and email fields as autocompletion
242  * queries would mainly rely on this.
243  *
244  * Add sort keys for name fields as those are likely targets for
245  * cursor usage.
246  */
247 static EContactField default_indexed_fields[] = {
248 	E_CONTACT_FULL_NAME,
249 	E_CONTACT_NICKNAME,
250 	E_CONTACT_FILE_AS,
251 	E_CONTACT_GIVEN_NAME,
252 	E_CONTACT_FAMILY_NAME,
253 	E_CONTACT_EMAIL,
254 	E_CONTACT_FILE_AS,
255 	E_CONTACT_FAMILY_NAME,
256 	E_CONTACT_GIVEN_NAME
257 };
258 
259 static EBookIndexType default_index_types[] = {
260 	E_BOOK_INDEX_PREFIX,
261 	E_BOOK_INDEX_PREFIX,
262 	E_BOOK_INDEX_PREFIX,
263 	E_BOOK_INDEX_PREFIX,
264 	E_BOOK_INDEX_PREFIX,
265 	E_BOOK_INDEX_PREFIX,
266 	E_BOOK_INDEX_SORT_KEY,
267 	E_BOOK_INDEX_SORT_KEY,
268 	E_BOOK_INDEX_SORT_KEY
269 };
270 
271 /******************************************************
272  *                  Summary Fields                    *
273  ******************************************************/
274 
275 static ECacheColumnInfo *
column_info_new(SummaryField * field,const gchar * column_name,const gchar * column_type,const gchar * idx_prefix)276 column_info_new (SummaryField *field,
277                  const gchar *column_name,
278                  const gchar *column_type,
279                  const gchar *idx_prefix)
280 {
281 	ECacheColumnInfo *info;
282 	gchar *index = NULL;
283 
284 	g_return_val_if_fail (column_name != NULL, NULL);
285 
286 	if (field->type == E_TYPE_CONTACT_ATTR_LIST)
287 		column_name = "value";
288 
289 	if (!column_type) {
290 		if (field->type == G_TYPE_STRING)
291 			column_type = "TEXT";
292 		else if (field->type == G_TYPE_BOOLEAN || field->type == E_TYPE_CONTACT_CERT)
293 			column_type = "INTEGER";
294 		else if (field->type == E_TYPE_CONTACT_ATTR_LIST)
295 			column_type = "TEXT";
296 		else
297 			g_warn_if_reached ();
298 	}
299 
300 	if (idx_prefix)
301 		index = g_strconcat (idx_prefix, "_", field->dbname, NULL);
302 
303 	info = e_cache_column_info_new (column_name, column_type, index);
304 
305 	g_free (index);
306 
307 	return info;
308 }
309 
310 static gint
summary_field_array_index(GArray * array,EContactField field)311 summary_field_array_index (GArray *array,
312                            EContactField field)
313 {
314 	gint ii;
315 
316 	for (ii = 0; ii < array->len; ii++) {
317 		SummaryField *iter = &g_array_index (array, SummaryField, ii);
318 		if (field == iter->field_id)
319 			return ii;
320 	}
321 
322 	return -1;
323 }
324 
325 static SummaryField *
summary_field_append(GArray * array,EContactField field_id,GError ** error)326 summary_field_append (GArray *array,
327 		      EContactField field_id,
328 		      GError **error)
329 {
330 	const gchar *dbname = NULL;
331 	GType type = G_TYPE_INVALID;
332 	gint idx;
333 	SummaryField new_field = { 0, };
334 
335 	if (field_id < 1 || field_id >= E_CONTACT_FIELD_LAST) {
336 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_UNSUPPORTED_FIELD,
337 			_("Unsupported contact field “%d” specified in summary"),
338 			field_id);
339 
340 		return NULL;
341 	}
342 
343 	/* Avoid including the same field twice in the summary */
344 	idx = summary_field_array_index (array, field_id);
345 	if (idx >= 0)
346 		return &g_array_index (array, SummaryField, idx);
347 
348 	/* Resolve some exceptions, we store these
349 	 * specific contact fields with different names
350 	 * than those found in the EContactField table
351 	 */
352 	switch (field_id) {
353 	case E_CONTACT_UID:
354 	case E_CONTACT_REV:
355 		/* Skip these, it's already in the ECache */
356 		return NULL;
357 	case E_CONTACT_IS_LIST:
358 		dbname = "is_list";
359 		break;
360 	default:
361 		dbname = e_contact_field_name (field_id);
362 		break;
363 	}
364 
365 	type = e_contact_field_type (field_id);
366 
367 	if (type != G_TYPE_STRING &&
368 	    type != G_TYPE_BOOLEAN &&
369 	    type != E_TYPE_CONTACT_CERT &&
370 	    type != E_TYPE_CONTACT_ATTR_LIST) {
371 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_UNSUPPORTED_FIELD,
372 			_("Contact field “%s” of type “%s” specified in summary, "
373 			"but only boolean, string and string list field types are supported"),
374 			e_contact_pretty_name (field_id), g_type_name (type));
375 
376 		return NULL;
377 	}
378 
379 	if (type == E_TYPE_CONTACT_ATTR_LIST) {
380 		new_field.aux_table = g_strconcat ("attrlist", "_", dbname, "_list", NULL);
381 		new_field.aux_table_symbolic = g_strconcat (dbname, "_list", NULL);
382 	}
383 
384 	new_field.field_id = field_id;
385 	new_field.dbname = dbname;
386 	new_field.type = type;
387 	new_field.index = 0;
388 
389 	g_array_append_val (array, new_field);
390 
391 	return &g_array_index (array, SummaryField, array->len - 1);
392 }
393 
394 static void
summary_fields_add_indexes(GArray * array,EContactField * indexes,EBookIndexType * index_types,gint n_indexes)395 summary_fields_add_indexes (GArray *array,
396                             EContactField *indexes,
397                             EBookIndexType *index_types,
398                             gint n_indexes)
399 {
400 	gint ii, jj;
401 
402 	for (ii = 0; ii < array->len; ii++) {
403 		SummaryField *sfield = &g_array_index (array, SummaryField, ii);
404 
405 		for (jj = 0; jj < n_indexes; jj++) {
406 			if (sfield->field_id == indexes[jj])
407 				sfield->index |= (1 << index_types[jj]);
408 
409 		}
410 	}
411 }
412 
413 static inline gint
summary_field_get_index(EBookCache * book_cache,EContactField field_id)414 summary_field_get_index (EBookCache *book_cache,
415                          EContactField field_id)
416 {
417 	gint ii;
418 
419 	for (ii = 0; ii < book_cache->priv->n_summary_fields; ii++) {
420 		if (book_cache->priv->summary_fields[ii].field_id == field_id)
421 			return ii;
422 	}
423 
424 	return -1;
425 }
426 
427 static inline SummaryField *
summary_field_get(EBookCache * book_cache,EContactField field_id)428 summary_field_get (EBookCache *book_cache,
429                    EContactField field_id)
430 {
431 	gint index;
432 
433 	index = summary_field_get_index (book_cache, field_id);
434 	if (index >= 0)
435 		return &(book_cache->priv->summary_fields[index]);
436 
437 	return NULL;
438 }
439 
440 static void
summary_field_init_dbnames(SummaryField * field)441 summary_field_init_dbnames (SummaryField *field)
442 {
443 	if (field->type == G_TYPE_STRING && (field->index & INDEX_FLAG (SORT_KEY))) {
444 		field->dbname_idx_sort_key = g_strconcat (field->dbname, "_", EBC_SUFFIX_SORT_KEY, NULL);
445 	}
446 
447 	if (field->type != G_TYPE_BOOLEAN && field->type != E_TYPE_CONTACT_CERT &&
448 	    (field->index & INDEX_FLAG (SUFFIX)) != 0) {
449 		field->dbname_idx_suffix = g_strconcat (field->dbname, "_", EBC_SUFFIX_REVERSE, NULL);
450 	}
451 
452 	if (field->type != G_TYPE_BOOLEAN && field->type != E_TYPE_CONTACT_CERT &&
453 	    (field->index & INDEX_FLAG (PHONE)) != 0) {
454 		field->dbname_idx_phone = g_strconcat (field->dbname, "_", EBC_SUFFIX_PHONE, NULL);
455 		field->dbname_idx_country = g_strconcat (field->dbname, "_", EBC_SUFFIX_COUNTRY, NULL);
456 	}
457 }
458 
459 static void
summary_field_prepend_columns(SummaryField * field,GSList ** out_columns)460 summary_field_prepend_columns (SummaryField *field,
461 			       GSList **out_columns)
462 {
463 	ECacheColumnInfo *info;
464 
465 	/* Doesn't hurt to verify a bit more here, this shouldn't happen though */
466 	g_return_if_fail (
467 		field->type == G_TYPE_STRING ||
468 		field->type == G_TYPE_BOOLEAN ||
469 		field->type == E_TYPE_CONTACT_CERT ||
470 		field->type == E_TYPE_CONTACT_ATTR_LIST);
471 
472 	/* Normal / default column */
473 	info = column_info_new (field, field->dbname, NULL,
474 		(field->index & INDEX_FLAG (PREFIX)) != 0 ? "INDEX" : NULL);
475 	*out_columns = g_slist_prepend (*out_columns, info);
476 
477 	/* Localized column, for storing sort keys */
478 	if (field->type == G_TYPE_STRING && (field->index & INDEX_FLAG (SORT_KEY))) {
479 		info = column_info_new (field, field->dbname_idx_sort_key, "TEXT", "SINDEX");
480 		*out_columns = g_slist_prepend (*out_columns, info);
481 	}
482 
483 	/* Suffix match column */
484 	if (field->type != G_TYPE_BOOLEAN && field->type != E_TYPE_CONTACT_CERT &&
485 	    (field->index & INDEX_FLAG (SUFFIX)) != 0) {
486 		info = column_info_new (field, field->dbname_idx_suffix, "TEXT", "RINDEX");
487 		*out_columns = g_slist_prepend (*out_columns, info);
488 	}
489 
490 	/* Phone match columns */
491 	if (field->type != G_TYPE_BOOLEAN && field->type != E_TYPE_CONTACT_CERT &&
492 	    (field->index & INDEX_FLAG (PHONE)) != 0) {
493 
494 		/* One indexed column for storing the national number */
495 		info = column_info_new (field, field->dbname_idx_phone, "TEXT", "PINDEX");
496 		*out_columns = g_slist_prepend (*out_columns, info);
497 
498 		/* One integer column for storing the country code */
499 		info = column_info_new (field, field->dbname_idx_country, "INTEGER DEFAULT 0", NULL);
500 		*out_columns = g_slist_prepend (*out_columns, info);
501 	}
502 }
503 
504 static void
summary_fields_array_free(SummaryField * fields,gint n_fields)505 summary_fields_array_free (SummaryField *fields,
506                            gint n_fields)
507 {
508 	gint ii;
509 
510 	for (ii = 0; ii < n_fields; ii++) {
511 		g_free (fields[ii].dbname_idx_suffix);
512 		g_free (fields[ii].dbname_idx_phone);
513 		g_free (fields[ii].dbname_idx_country);
514 		g_free (fields[ii].dbname_idx_sort_key);
515 		g_free (fields[ii].aux_table);
516 		g_free (fields[ii].aux_table_symbolic);
517 	}
518 
519 	g_free (fields);
520 }
521 
522 /******************************************************
523  *       Functions installed into the SQLite          *
524  ******************************************************/
525 
526 /* Implementation for REGEXP keyword */
527 static void
ebc_regexp(sqlite3_context * context,gint argc,sqlite3_value ** argv)528 ebc_regexp (sqlite3_context *context,
529 	    gint argc,
530 	    sqlite3_value **argv)
531 {
532 	GRegex *regex;
533 	const gchar *expression;
534 	const gchar *text;
535 
536 	/* Reuse the same GRegex for all REGEXP queries with the same expression */
537 	regex = sqlite3_get_auxdata (context, 0);
538 	if (!regex) {
539 		GError *error = NULL;
540 
541 		expression = (const gchar *) sqlite3_value_text (argv[0]);
542 
543 		regex = g_regex_new (expression, 0, 0, &error);
544 
545 		if (!regex) {
546 			sqlite3_result_error (
547 				context,
548 				error ? error->message :
549 				_("Error parsing regular expression"),
550 				-1);
551 			g_clear_error (&error);
552 			return;
553 		}
554 
555 		/* SQLite will take care of freeing the GRegex when we're done with the query */
556 		sqlite3_set_auxdata (context, 0, regex, (GDestroyNotify) g_regex_unref);
557 	}
558 
559 	/* Now perform the comparison */
560 	text = (const gchar *) sqlite3_value_text (argv[1]);
561 	if (text != NULL) {
562 		gboolean match;
563 
564 		match = g_regex_match (regex, text, 0, NULL);
565 		sqlite3_result_int (context, match ? 1 : 0);
566 	}
567 }
568 
569 /* Implementation of EBC_FUNC_COMPARE_VCARD (fallback for non-summary queries) */
570 static void
ebc_compare_vcard(sqlite3_context * context,gint argc,sqlite3_value ** argv)571 ebc_compare_vcard (sqlite3_context *context,
572 		   gint argc,
573 		   sqlite3_value **argv)
574 {
575 	EBookBackendSExp *sexp = NULL;
576 	const gchar *text;
577 	const gchar *vcard;
578 
579 	/* Reuse the same sexp for all queries with the same search expression */
580 	sexp = sqlite3_get_auxdata (context, 0);
581 	if (!sexp) {
582 
583 		/* The first argument will be reused for many rows */
584 		text = (const gchar *) sqlite3_value_text (argv[0]);
585 		if (text) {
586 			sexp = e_book_backend_sexp_new (text);
587 			sqlite3_set_auxdata (
588 				context, 0,
589 				sexp,
590 				g_object_unref);
591 		}
592 
593 		/* This shouldn't happen, catch invalid sexp in preflight */
594 		if (!sexp) {
595 			sqlite3_result_int (context, 0);
596 			return;
597 		}
598 
599 	}
600 
601 	/* Reuse the same vcard as much as possible (it can be referred to more than
602 	 * once in the query, so it can be reused for multiple comparisons on the same row)
603 	 */
604 	vcard = sqlite3_get_auxdata (context, 1);
605 	if (!vcard) {
606 		vcard = (const gchar *) sqlite3_value_text (argv[1]);
607 
608 		if (vcard)
609 			sqlite3_set_auxdata (context, 1, g_strdup (vcard), g_free);
610 	}
611 
612 	/* A NULL vcard can never match */
613 	if (!vcard || !*vcard) {
614 		sqlite3_result_int (context, 0);
615 		return;
616 	}
617 
618 	/* Compare this vcard */
619 	if (e_book_backend_sexp_match_vcard (sexp, vcard))
620 		sqlite3_result_int (context, 1);
621 	else
622 		sqlite3_result_int (context, 0);
623 }
624 
625 static void
ebc_eqphone(sqlite3_context * context,gint argc,sqlite3_value ** argv,EPhoneNumberMatch requested_match)626 ebc_eqphone (sqlite3_context *context,
627 	     gint argc,
628 	     sqlite3_value **argv,
629 	     EPhoneNumberMatch requested_match)
630 {
631 	EBookCache *ebc = sqlite3_user_data (context);
632 	EPhoneNumber *input_phone = NULL, *row_phone = NULL;
633 	EPhoneNumberMatch match = E_PHONE_NUMBER_MATCH_NONE;
634 	const gchar *text;
635 
636 	/* Reuse the same phone number for all queries with the same phone number argument */
637 	input_phone = sqlite3_get_auxdata (context, 0);
638 	if (!input_phone) {
639 
640 		/* The first argument will be reused for many rows */
641 		text = (const gchar *) sqlite3_value_text (argv[0]);
642 		if (text) {
643 
644 			/* Ignore errors, they are fine for phone numbers */
645 			input_phone = e_phone_number_from_string (text, ebc->priv->region_code, NULL);
646 
647 			/* SQLite will take care of freeing the EPhoneNumber when we're done with the expression */
648 			if (input_phone)
649 				sqlite3_set_auxdata (
650 					context, 0,
651 					input_phone,
652 					(GDestroyNotify) e_phone_number_free);
653 		}
654 	}
655 
656 	/* This shouldn't happen, as we catch invalid phone number queries in preflight
657 	 */
658 	if (!input_phone) {
659 		sqlite3_result_int (context, 0);
660 		return;
661 	}
662 
663 	/* Parse the phone number for this row */
664 	text = (const gchar *) sqlite3_value_text (argv[1]);
665 	if (text != NULL) {
666 		row_phone = e_phone_number_from_string (text, ebc->priv->region_code, NULL);
667 
668 		/* And perform the comparison */
669 		if (row_phone) {
670 			match = e_phone_number_compare (input_phone, row_phone);
671 
672 			e_phone_number_free (row_phone);
673 		}
674 	}
675 
676 	/* Now report the result */
677 	if (match != E_PHONE_NUMBER_MATCH_NONE &&
678 	    match <= requested_match)
679 		sqlite3_result_int (context, 1);
680 	else
681 		sqlite3_result_int (context, 0);
682 }
683 
684 /* Exact phone number match function: EBC_FUNC_EQPHONE_EXACT */
685 static void
ebc_eqphone_exact(sqlite3_context * context,gint argc,sqlite3_value ** argv)686 ebc_eqphone_exact (sqlite3_context *context,
687 		   gint argc,
688 		   sqlite3_value **argv)
689 {
690 	ebc_eqphone (context, argc, argv, E_PHONE_NUMBER_MATCH_EXACT);
691 }
692 
693 /* National phone number match function: EBC_FUNC_EQPHONE_NATIONAL */
694 static void
ebc_eqphone_national(sqlite3_context * context,gint argc,sqlite3_value ** argv)695 ebc_eqphone_national (sqlite3_context *context,
696 		      gint argc,
697 		      sqlite3_value **argv)
698 {
699 	ebc_eqphone (context, argc, argv, E_PHONE_NUMBER_MATCH_NATIONAL);
700 }
701 
702 /* Short phone number match function: EBC_FUNC_EQPHONE_SHORT */
703 static void
ebc_eqphone_short(sqlite3_context * context,gint argc,sqlite3_value ** argv)704 ebc_eqphone_short (sqlite3_context *context,
705 		   gint argc,
706 		   sqlite3_value **argv)
707 {
708 	ebc_eqphone (context, argc, argv, E_PHONE_NUMBER_MATCH_SHORT);
709 }
710 
711 typedef void	(*EBCCustomFunc)	(sqlite3_context *context,
712 					 gint argc,
713 					 sqlite3_value **argv);
714 
715 typedef struct {
716 	const gchar *name;
717 	EBCCustomFunc func;
718 	gint arguments;
719 } EBCCustomFuncTab;
720 
721 static EBCCustomFuncTab ebc_custom_functions[] = {
722 	{ "regexp",                  ebc_regexp,           2 }, /* regexp (expression, column_data) */
723 	{ EBC_FUNC_COMPARE_VCARD,    ebc_compare_vcard,    2 }, /* compare_vcard (sexp, vcard) */
724 	{ EBC_FUNC_EQPHONE_EXACT,    ebc_eqphone_exact,    2 }, /* eqphone_exact (search_input, column_data) */
725 	{ EBC_FUNC_EQPHONE_NATIONAL, ebc_eqphone_national, 2 }, /* eqphone_national (search_input, column_data) */
726 	{ EBC_FUNC_EQPHONE_SHORT,    ebc_eqphone_short,    2 }, /* eqphone_national (search_input, column_data) */
727 };
728 
729 /******************************************************
730  *            Fallback Collation Sequences            *
731  ******************************************************
732  *
733  * The fallback simply compares vcards, vcards which have been
734  * stored on the cursor will have a preencoded key (these
735  * utilities encode & decode that key).
736  */
737 static gchar *
ebc_encode_vcard_sort_key(const gchar * sort_key)738 ebc_encode_vcard_sort_key (const gchar *sort_key)
739 {
740 	EVCard *vcard = e_vcard_new ();
741 	gchar *base64;
742 	gchar *encoded;
743 
744 	/* Encode this otherwise e-vcard messes it up */
745 	base64 = g_base64_encode ((const guchar *) sort_key, strlen (sort_key));
746 	e_vcard_append_attribute_with_value (
747 		vcard,
748 		e_vcard_attribute_new (NULL, EBC_VCARD_SORT_KEY),
749 		base64);
750 	encoded = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30);
751 
752 	g_free (base64);
753 	g_object_unref (vcard);
754 
755 	return encoded;
756 }
757 
758 static gchar *
ebc_decode_vcard_sort_key_from_vcard(EVCard * vcard)759 ebc_decode_vcard_sort_key_from_vcard (EVCard *vcard)
760 {
761 	EVCardAttribute *attr;
762 	GList *values = NULL;
763 	gchar *sort_key = NULL;
764 	gchar *base64 = NULL;
765 
766 	attr = e_vcard_get_attribute (vcard, EBC_VCARD_SORT_KEY);
767 	if (attr)
768 		values = e_vcard_attribute_get_values (attr);
769 
770 	if (values && values->data) {
771 		gsize len;
772 
773 		base64 = g_strdup (values->data);
774 
775 		sort_key = (gchar *) g_base64_decode (base64, &len);
776 		g_free (base64);
777 	}
778 
779 	return sort_key;
780 }
781 
782 static gchar *
ebc_decode_vcard_sort_key(const gchar * encoded)783 ebc_decode_vcard_sort_key (const gchar *encoded)
784 {
785 	EVCard *vcard;
786 	gchar *sort_key;
787 
788 	vcard = e_vcard_new_from_string (encoded);
789 	sort_key = ebc_decode_vcard_sort_key_from_vcard (vcard);
790 	g_object_unref (vcard);
791 
792 	return sort_key;
793 }
794 
795 static gchar *
convert_phone(const gchar * normal,const gchar * region_code,gint * out_country_code)796 convert_phone (const gchar *normal,
797                const gchar *region_code,
798                gint *out_country_code)
799 {
800 	EPhoneNumber *number = NULL;
801 	gchar *national_number = NULL;
802 	gint country_code = 0;
803 
804 	/* Don't warn about erronous phone number strings, it's a perfectly normal
805 	 * use case for users to enter notes instead of phone numbers in the phone
806 	 * number contact fields, such as "Ask Jenny for Lisa's phone number"
807 	 */
808 	if (normal && e_phone_number_is_supported ())
809 		number = e_phone_number_from_string (normal, region_code, NULL);
810 
811 	if (number) {
812 		EPhoneNumberCountrySource source;
813 
814 		national_number = e_phone_number_get_national_number (number);
815 		country_code = e_phone_number_get_country_code (number, &source);
816 		e_phone_number_free (number);
817 
818 		if (source == E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT)
819 			country_code = 0;
820 	}
821 
822 	if (out_country_code)
823 		*out_country_code = country_code;
824 
825 	return national_number;
826 }
827 
828 static gchar *
remove_leading_zeros(gchar * number)829 remove_leading_zeros (gchar *number)
830 {
831 	gchar *trimmed = NULL;
832 	gchar *tmp = number;
833 
834 	g_return_val_if_fail (NULL != number, NULL);
835 
836 	while ('0' == *tmp)
837 		tmp++;
838 	trimmed = g_strdup (tmp);
839 	g_free (number);
840 
841 	return trimmed;
842 }
843 
844 static void
ebc_fill_other_columns(EBookCache * book_cache,EContact * contact,ECacheColumnValues * other_columns)845 ebc_fill_other_columns (EBookCache *book_cache,
846 			EContact *contact,
847 			ECacheColumnValues *other_columns)
848 {
849 	gint ii;
850 
851 	g_return_if_fail (E_IS_BOOK_CACHE (book_cache));
852 	g_return_if_fail (E_IS_CONTACT (contact));
853 	g_return_if_fail (other_columns != NULL);
854 
855 	for (ii = 0; ii < book_cache->priv->n_summary_fields; ii++) {
856 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
857 
858 		if (field->field_id == E_CONTACT_UID ||
859 		    field->field_id == E_CONTACT_REV) {
860 			continue;
861 		}
862 
863 		if (field->type == G_TYPE_STRING) {
864 			gchar *val;
865 			gchar *normal;
866 			gchar *str;
867 
868 			val = e_contact_get (contact, field->field_id);
869 			normal = e_util_utf8_normalize (val);
870 
871 			e_cache_column_values_take_value (other_columns, field->dbname, normal);
872 
873 			if ((field->index & INDEX_FLAG (SORT_KEY)) != 0) {
874 				if (val)
875 					str = e_collator_generate_key (book_cache->priv->collator, val, NULL);
876 				else
877 					str = g_strdup ("");
878 
879 				e_cache_column_values_take_value (other_columns, field->dbname_idx_sort_key, str);
880 			}
881 
882 			if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
883 				if (normal)
884 					str = g_utf8_strreverse (normal, -1);
885 				else
886 					str = NULL;
887 
888 				e_cache_column_values_take_value (other_columns, field->dbname_idx_suffix, str);
889 			}
890 
891 			if ((field->index & INDEX_FLAG (PHONE)) != 0) {
892 				gint country_code;
893 
894 				str = convert_phone (normal, book_cache->priv->region_code, &country_code);
895 				str = remove_leading_zeros (str);
896 
897 				e_cache_column_values_take_value (other_columns, field->dbname_idx_phone, str);
898 
899 				str = g_strdup_printf ("%d", country_code);
900 
901 				e_cache_column_values_take_value (other_columns, field->dbname_idx_country, str);
902 			}
903 
904 			g_free (val);
905 		} else if (field->type == G_TYPE_BOOLEAN) {
906 			gboolean val;
907 
908 			val = e_contact_get (contact, field->field_id) ? TRUE : FALSE;
909 
910 			e_cache_column_values_take_value (other_columns, field->dbname, g_strdup_printf ("%d", val ? 1 : 0));
911 		} else if (field->type == E_TYPE_CONTACT_CERT) {
912 			EContactCert *cert = NULL;
913 
914 			cert = e_contact_get (contact, field->field_id);
915 
916 			/* We don't actually store the cert; only a boolean to indicate
917 			 * that is *has* a cert. */
918 			e_cache_column_values_take_value (other_columns, field->dbname, g_strdup_printf ("%d", cert ? 1 : 0));
919 			e_contact_cert_free (cert);
920 		} else if (field->type != E_TYPE_CONTACT_ATTR_LIST) {
921 			g_warn_if_reached ();
922 		}
923 	}
924 }
925 
926 static inline void
format_column_declaration(GString * string,ECacheColumnInfo * info)927 format_column_declaration (GString *string,
928 			   ECacheColumnInfo *info)
929 {
930 	g_string_append (string, info->name);
931 	g_string_append_c (string, ' ');
932 
933 	g_string_append (string, info->type);
934 
935 }
936 
937 static gboolean
ebc_init_aux_tables(EBookCache * book_cache,GCancellable * cancellable,GError ** error)938 ebc_init_aux_tables (EBookCache *book_cache,
939 		     GCancellable *cancellable,
940 		     GError **error)
941 {
942 	GString *string;
943 	gboolean success = TRUE;
944 	gchar *tmp;
945 	gint ii;
946 
947 	for (ii = 0; success && ii < book_cache->priv->n_summary_fields; ii++) {
948 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
949 		GSList *aux_columns = NULL, *link;
950 
951 		if (field->type != E_TYPE_CONTACT_ATTR_LIST)
952 			continue;
953 
954 		summary_field_prepend_columns (field, &aux_columns);
955 		if (!aux_columns)
956 			continue;
957 
958 		/* Create the auxiliary table for this multi valued field */
959 		string = g_string_sized_new (
960 			COLUMN_DEFINITION_BYTES * 3 +
961 			COLUMN_DEFINITION_BYTES * g_slist_length (aux_columns));
962 
963 		e_cache_sqlite_stmt_append_printf (string, "CREATE TABLE IF NOT EXISTS %Q (uid TEXT NOT NULL REFERENCES " E_CACHE_TABLE_OBJECTS
964 						  " (" E_CACHE_COLUMN_UID ")",
965 						  field->aux_table);
966 		for (link = aux_columns; link; link = g_slist_next (link)) {
967 			ECacheColumnInfo *info = link->data;
968 
969 			g_string_append (string, ", ");
970 			format_column_declaration (string, info);
971 		}
972 		g_string_append_c (string, ')');
973 
974 		success = e_cache_sqlite_exec (E_CACHE (book_cache), string->str, cancellable, error);
975 		g_string_free (string, TRUE);
976 
977 		if (success) {
978 			/* Create an index on the implied 'uid' column, this is important
979 			 * when replacing (modifying) contacts, since we need to remove
980 			 * all rows in an auxiliary table which matches a given UID.
981 			 *
982 			 * This index speeds up the constraint in a statement such as:
983 			 *
984 			 *   DELETE from email_list WHERE email_list.uid = 'contact uid'
985 			 */
986 			tmp = e_cache_sqlite_stmt_printf ("CREATE INDEX IF NOT EXISTS UID_INDEX_%s_%s ON %Q (uid)",
987 				field->dbname, field->aux_table, field->aux_table);
988 			success = e_cache_sqlite_exec (E_CACHE (book_cache), tmp, cancellable, error);
989 			e_cache_sqlite_stmt_free (tmp);
990 		}
991 
992 		/* Add indexes to columns in this auxiliary table
993 		 */
994 		for (link = aux_columns; success && link; link = g_slist_next (link)) {
995 			ECacheColumnInfo *info = link->data;
996 
997 			if (info->index_name) {
998 				tmp = e_cache_sqlite_stmt_printf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (%s)",
999 					info->index_name, field->aux_table, info->name);
1000 				success = e_cache_sqlite_exec (E_CACHE (book_cache), tmp, cancellable, error);
1001 				e_cache_sqlite_stmt_free (tmp);
1002 			}
1003 		}
1004 
1005 		g_slist_free_full (aux_columns, e_cache_column_info_free);
1006 	}
1007 
1008 	return success;
1009 }
1010 
1011 static gboolean
ebc_run_multi_insert_one(ECache * cache,SummaryField * field,const gchar * uid,const gchar * value,GCancellable * cancellable,GError ** error)1012 ebc_run_multi_insert_one (ECache *cache,
1013                           SummaryField *field,
1014                           const gchar *uid,
1015                           const gchar *value,
1016 			  GCancellable *cancellable,
1017                           GError **error)
1018 {
1019 	GString *stmt, *values;
1020 	gchar *normal;
1021 	gboolean success;
1022 
1023 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1024 	g_return_val_if_fail (field != NULL, FALSE);
1025 	g_return_val_if_fail (uid != NULL, FALSE);
1026 
1027 	stmt = g_string_sized_new (INSERT_MULTI_STMT_BYTES);
1028 	values = g_string_sized_new (INSERT_MULTI_STMT_BYTES);
1029 
1030 	normal = e_util_utf8_normalize (value);
1031 
1032 	e_cache_sqlite_stmt_append_printf (stmt, "INSERT INTO %Q (uid, value", field->aux_table);
1033 
1034 	if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
1035 		g_string_append (stmt, ", value_" EBC_SUFFIX_REVERSE);
1036 
1037 		if (normal) {
1038 			gchar *str;
1039 
1040 			str = g_utf8_strreverse (normal, -1);
1041 
1042 			e_cache_sqlite_stmt_append_printf (values, ", %Q", str);
1043 
1044 			g_free (str);
1045 		} else {
1046 			g_string_append (values, ", NULL");
1047 		}
1048 	}
1049 
1050 	if ((field->index & INDEX_FLAG (PHONE)) != 0) {
1051 		EBookCache *book_cache;
1052 		gint country_code = 0;
1053 		gchar *str;
1054 
1055 		g_string_append (stmt, ", value_" EBC_SUFFIX_PHONE);
1056 		g_string_append (stmt, ", value_" EBC_SUFFIX_COUNTRY);
1057 
1058 		book_cache = E_BOOK_CACHE (cache);
1059 		str = convert_phone (normal, book_cache->priv->region_code, &country_code);
1060 		str = remove_leading_zeros (str);
1061 
1062 		if (str) {
1063 			e_cache_sqlite_stmt_append_printf (values, ", %Q", str);
1064 		} else {
1065 			g_string_append (values, ",NULL");
1066 		}
1067 
1068 		g_string_append_printf (values, ",%d", country_code);
1069 	}
1070 
1071 	e_cache_sqlite_stmt_append_printf (stmt, ") VALUES (%Q, %Q", uid, normal);
1072 	g_free (normal);
1073 
1074 	g_string_append (stmt, values->str);
1075 	g_string_append_c (stmt, ')');
1076 
1077 	success = e_cache_sqlite_exec (cache, stmt->str, cancellable, error);
1078 
1079 	g_string_free (stmt, TRUE);
1080 	g_string_free (values, TRUE);
1081 
1082 	return success;
1083 }
1084 
1085 static gboolean
ebc_run_multi_insert(ECache * cache,SummaryField * field,const gchar * uid,EContact * contact,GCancellable * cancellable,GError ** error)1086 ebc_run_multi_insert (ECache *cache,
1087 		      SummaryField *field,
1088 		      const gchar *uid,
1089 		      EContact *contact,
1090 		      GCancellable *cancellable,
1091 		      GError **error)
1092 {
1093 	GList *values, *link;
1094 	gboolean success = TRUE;
1095 
1096 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1097 	g_return_val_if_fail (field != NULL, FALSE);
1098 	g_return_val_if_fail (uid != NULL, FALSE);
1099 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1100 
1101 	values = e_contact_get (contact, field->field_id);
1102 
1103 	for (link = values; success && link; link = g_list_next (link)) {
1104 		const gchar *value = link->data;
1105 
1106 		success = ebc_run_multi_insert_one (cache, field, uid, value, cancellable, error);
1107 	}
1108 
1109 	/* Free the list of allocated strings */
1110 	e_contact_attr_list_free (values);
1111 
1112 	return success;
1113 }
1114 
1115 static gboolean
ebc_run_multi_delete(ECache * cache,SummaryField * field,const gchar * uid,GCancellable * cancellable,GError ** error)1116 ebc_run_multi_delete (ECache *cache,
1117 		      SummaryField *field,
1118 		      const gchar *uid,
1119 		      GCancellable *cancellable,
1120 		      GError **error)
1121 {
1122 	gchar *stmt;
1123 	gboolean success;
1124 
1125 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1126 	g_return_val_if_fail (field != NULL, FALSE);
1127 	g_return_val_if_fail (uid != NULL, FALSE);
1128 
1129 	stmt = e_cache_sqlite_stmt_printf ("DELETE FROM %Q WHERE uid=%Q", field->aux_table, uid);
1130 	success = e_cache_sqlite_exec (cache, stmt, cancellable, error);
1131 	e_cache_sqlite_stmt_free (stmt);
1132 
1133 	return success;
1134 }
1135 
1136 static gboolean
ebc_update_aux_tables(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,GCancellable * cancellable,GError ** error)1137 ebc_update_aux_tables (ECache *cache,
1138 		       const gchar *uid,
1139 		       const gchar *revision,
1140 		       const gchar *object,
1141 		       GCancellable *cancellable,
1142 		       GError **error)
1143 {
1144 	EBookCache *book_cache;
1145 	EContact *contact = NULL;
1146 	gint ii;
1147 	gboolean success = TRUE;
1148 
1149 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1150 
1151 	book_cache = E_BOOK_CACHE (cache);
1152 
1153 	for (ii = 0; ii < book_cache->priv->n_summary_fields && success; ii++) {
1154 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
1155 
1156 		if (field->type != E_TYPE_CONTACT_ATTR_LIST)
1157 			continue;
1158 
1159 		if (!contact) {
1160 			contact = e_contact_new_from_vcard_with_uid (object, uid);
1161 			success = contact != NULL;
1162 		}
1163 
1164 		success = success && ebc_run_multi_delete (cache, field, uid, cancellable, error);
1165 		success = success && ebc_run_multi_insert (cache, field, uid, contact, cancellable, error);
1166 	}
1167 
1168 	g_clear_object (&contact);
1169 
1170 	return success;
1171 }
1172 
1173 static gboolean
ebc_delete_from_aux_tables(ECache * cache,const gchar * uid,GCancellable * cancellable,GError ** error)1174 ebc_delete_from_aux_tables (ECache *cache,
1175 			    const gchar *uid,
1176 			    GCancellable *cancellable,
1177 			    GError **error)
1178 {
1179 	EBookCache *book_cache;
1180 	gint ii;
1181 	gboolean success = TRUE;
1182 
1183 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1184 	g_return_val_if_fail (uid != NULL, FALSE);
1185 
1186 	book_cache = E_BOOK_CACHE (cache);
1187 
1188 	for (ii = 0; ii < book_cache->priv->n_summary_fields && success; ii++) {
1189 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
1190 
1191 		if (field->type != E_TYPE_CONTACT_ATTR_LIST)
1192 			continue;
1193 
1194 		success = success && ebc_run_multi_delete (cache, field, uid, cancellable, error);
1195 	}
1196 
1197 	return success;
1198 }
1199 
1200 static gboolean
ebc_delete_from_aux_tables_offline_deleted(ECache * cache,GCancellable * cancellable,GError ** error)1201 ebc_delete_from_aux_tables_offline_deleted (ECache *cache,
1202 					    GCancellable *cancellable,
1203 					    GError **error)
1204 {
1205 	EBookCache *book_cache;
1206 	gint ii;
1207 	gboolean success = TRUE;
1208 
1209 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1210 
1211 	book_cache = E_BOOK_CACHE (cache);
1212 
1213 	for (ii = 0; ii < book_cache->priv->n_summary_fields && success; ii++) {
1214 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
1215 		gchar *stmt;
1216 
1217 		if (field->type != E_TYPE_CONTACT_ATTR_LIST)
1218 			continue;
1219 
1220 		stmt = e_cache_sqlite_stmt_printf ("DELETE FROM %Q WHERE uid IN ("
1221 			"SELECT " E_CACHE_COLUMN_UID " FROM " E_CACHE_TABLE_OBJECTS
1222 			" WHERE " E_CACHE_COLUMN_STATE "=%d)",
1223 			field->aux_table, E_OFFLINE_STATE_LOCALLY_DELETED);
1224 
1225 		success = e_cache_sqlite_exec (cache, stmt, cancellable, error);
1226 
1227 		e_cache_sqlite_stmt_free (stmt);
1228 	}
1229 
1230 	return success;
1231 }
1232 
1233 static gboolean
ebc_empty_aux_tables(ECache * cache,GCancellable * cancellable,GError ** error)1234 ebc_empty_aux_tables (ECache *cache,
1235 		      GCancellable *cancellable,
1236 		      GError **error)
1237 {
1238 	EBookCache *book_cache;
1239 	gint ii;
1240 	gboolean success = TRUE;
1241 
1242 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1243 
1244 	book_cache = E_BOOK_CACHE (cache);
1245 
1246 	for (ii = 0; ii < book_cache->priv->n_summary_fields && success; ii++) {
1247 		SummaryField *field = &(book_cache->priv->summary_fields[ii]);
1248 		gchar *stmt;
1249 
1250 		if (field->type != E_TYPE_CONTACT_ATTR_LIST)
1251 			continue;
1252 
1253 		stmt = e_cache_sqlite_stmt_printf ("DELETE FROM %Q", field->aux_table);
1254 		success = e_cache_sqlite_exec (cache, stmt, cancellable, error);
1255 		e_cache_sqlite_stmt_free (stmt);
1256 	}
1257 
1258 	return success;
1259 }
1260 
1261 static gboolean
ebc_upgrade_cb(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,EOfflineState offline_state,gint ncols,const gchar * column_names[],const gchar * column_values[],gchar ** out_revision,gchar ** out_object,EOfflineState * out_offline_state,ECacheColumnValues ** out_other_columns,gpointer user_data)1262 ebc_upgrade_cb (ECache *cache,
1263 		const gchar *uid,
1264 		const gchar *revision,
1265 		const gchar *object,
1266 		EOfflineState offline_state,
1267 		gint ncols,
1268 		const gchar *column_names[],
1269 		const gchar *column_values[],
1270 		gchar **out_revision,
1271 		gchar **out_object,
1272 		EOfflineState *out_offline_state,
1273 		ECacheColumnValues **out_other_columns,
1274 		gpointer user_data)
1275 {
1276 	EContact *contact;
1277 	ECacheColumnValues *other_columns;
1278 
1279 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
1280 
1281 	contact = e_contact_new_from_vcard_with_uid (object, uid);
1282 
1283 	/* Ignore broken rows? */
1284 	if (!contact)
1285 		return TRUE;
1286 
1287 	other_columns = e_cache_column_values_new ();
1288 
1289 	ebc_fill_other_columns (E_BOOK_CACHE (cache), contact, other_columns);
1290 
1291 	g_clear_object (&contact);
1292 
1293 	/* This will cause rewrite even when no values changed, but it's
1294 	   necessary, because the locale changed, which can influence
1295 	   other tables, not only the other columns. */
1296 	*out_other_columns = other_columns;
1297 
1298 	return TRUE;
1299 }
1300 
1301 /* Called with the lock held and inside a transaction */
1302 static gboolean
ebc_upgrade(EBookCache * book_cache,GCancellable * cancellable,GError ** error)1303 ebc_upgrade (EBookCache *book_cache,
1304 	     GCancellable *cancellable,
1305 	     GError **error)
1306 {
1307 	gboolean success;
1308 
1309 	success = e_cache_foreach_update (E_CACHE (book_cache), E_CACHE_EXCLUDE_DELETED, NULL,
1310 		ebc_upgrade_cb, NULL, cancellable, error);
1311 
1312 	/* Store the new locale & country code */
1313 	success = success && e_cache_set_key (E_CACHE (book_cache), EBC_KEY_LC_COLLATE, book_cache->priv->locale, error);
1314 	success = success && e_cache_set_key (E_CACHE (book_cache), EBC_KEY_COUNTRYCODE, book_cache->priv->region_code, error);
1315 
1316 	return success;
1317 }
1318 
1319 static gboolean
ebc_set_locale_internal(EBookCache * book_cache,const gchar * locale,GError ** error)1320 ebc_set_locale_internal (EBookCache *book_cache,
1321 			 const gchar *locale,
1322 			 GError **error)
1323 {
1324 	ECollator *collator;
1325 
1326 	g_return_val_if_fail (locale && locale[0], FALSE);
1327 
1328 	if (g_strcmp0 (book_cache->priv->locale, locale) != 0) {
1329 		gchar *country_code = NULL;
1330 
1331 		collator = e_collator_new_interpret_country (locale, &country_code, error);
1332 		if (collator == NULL)
1333 			return FALSE;
1334 
1335 		/* Assign region code parsed from the locale by ICU */
1336 		g_free (book_cache->priv->region_code);
1337 		book_cache->priv->region_code = country_code;
1338 
1339 		/* Assign locale */
1340 		g_free (book_cache->priv->locale);
1341 		book_cache->priv->locale = g_strdup (locale);
1342 
1343 		/* Assign collator */
1344 		if (book_cache->priv->collator)
1345 			e_collator_unref (book_cache->priv->collator);
1346 		book_cache->priv->collator = collator;
1347 	}
1348 
1349 	return TRUE;
1350 }
1351 
1352 static gboolean
ebc_init_locale(EBookCache * book_cache,GCancellable * cancellable,GError ** error)1353 ebc_init_locale (EBookCache *book_cache,
1354 		 GCancellable *cancellable,
1355 		 GError **error)
1356 {
1357 	gchar *stored_lc_collate;
1358 	gchar *stored_region_code;
1359 	const gchar *lc_collate;
1360 	gboolean success = TRUE;
1361 	gboolean relocalize_needed = FALSE;
1362 
1363 	/* Get the locale setting for this addressbook */
1364 	stored_lc_collate = e_cache_dup_key (E_CACHE (book_cache), EBC_KEY_LC_COLLATE, NULL);
1365 	stored_region_code = e_cache_dup_key (E_CACHE (book_cache), EBC_KEY_COUNTRYCODE, NULL);
1366 
1367 	lc_collate = stored_lc_collate;
1368 
1369 	/* When creating a new addressbook, or upgrading from a version
1370 	 * where we did not have any locale setting; default to system locale,
1371 	 * we must absolutely always have a locale set.
1372 	 */
1373 	if (!lc_collate || !lc_collate[0])
1374 		lc_collate = setlocale (LC_COLLATE, NULL);
1375 	if (!lc_collate || !lc_collate[0])
1376 		lc_collate = setlocale (LC_ALL, NULL);
1377 	if (!lc_collate || !lc_collate[0])
1378 		lc_collate = "en_US.utf8";
1379 
1380 	/* Before touching any data, make sure we have a valid ECollator,
1381 	 * this will also resolve our region code
1382 	 */
1383 	if (success)
1384 		success = ebc_set_locale_internal (book_cache, lc_collate, error);
1385 
1386 	/* Check if we need to relocalize */
1387 	if (success) {
1388 		/* We may need to relocalize for a country code change */
1389 		if (g_strcmp0 (book_cache->priv->region_code, stored_region_code) != 0)
1390 			relocalize_needed = TRUE;
1391 	}
1392 
1393 	/* Reinsert all contacts with new locale & country code */
1394 	if (success && relocalize_needed)
1395 		success = ebc_upgrade (book_cache, cancellable, error);
1396 
1397 	g_free (stored_region_code);
1398 	g_free (stored_lc_collate);
1399 
1400 	return success;
1401 }
1402 
1403 typedef struct {
1404 	EBookCache *book_cache;
1405 	EContactField field;
1406 } EBCCollData;
1407 
1408 static gint
ebc_fallback_collator(gpointer ref,gint len1,gconstpointer data1,gint len2,gconstpointer data2)1409 ebc_fallback_collator (gpointer ref,
1410 		       gint len1,
1411 		       gconstpointer data1,
1412 		       gint len2,
1413 		       gconstpointer data2)
1414 {
1415 	EBCCollData *data = ref;
1416 	EBookCache *book_cache;
1417 	EContact *contact1, *contact2;
1418 	const gchar *str1, *str2;
1419 	gchar *key1, *key2;
1420 	gchar *tmp;
1421 	gint result = 0;
1422 
1423 	book_cache = data->book_cache;
1424 
1425 	str1 = (const gchar *) data1;
1426 	str2 = (const gchar *) data2;
1427 
1428 	/* Construct 2 contacts (we're comparing vcards) */
1429 	contact1 = e_contact_new ();
1430 	contact2 = e_contact_new ();
1431 	e_vcard_construct_full (E_VCARD (contact1), str1, len1, NULL);
1432 	e_vcard_construct_full (E_VCARD (contact2), str2, len2, NULL);
1433 
1434 	/* Extract first key */
1435 	key1 = ebc_decode_vcard_sort_key_from_vcard (E_VCARD (contact1));
1436 	if (!key1) {
1437 		tmp = e_contact_get (contact1, data->field);
1438 		if (tmp)
1439 			key1 = e_collator_generate_key (book_cache->priv->collator, tmp, NULL);
1440 		g_free (tmp);
1441 	}
1442 	if (!key1)
1443 		key1 = g_strdup ("");
1444 
1445 	/* Extract second key */
1446 	key2 = ebc_decode_vcard_sort_key_from_vcard (E_VCARD (contact2));
1447 	if (!key2) {
1448 		tmp = e_contact_get (contact2, data->field);
1449 		if (tmp)
1450 			key2 = e_collator_generate_key (book_cache->priv->collator, tmp, NULL);
1451 		g_free (tmp);
1452 	}
1453 	if (!key2)
1454 		key2 = g_strdup ("");
1455 
1456 	result = strcmp (key1, key2);
1457 
1458 	g_free (key1);
1459 	g_free (key2);
1460 	g_object_unref (contact1);
1461 	g_object_unref (contact2);
1462 
1463 	return result;
1464 }
1465 
1466 static EBCCollData *
ebc_coll_data_new(EBookCache * book_cache,EContactField field)1467 ebc_coll_data_new (EBookCache *book_cache,
1468 		   EContactField field)
1469 {
1470 	EBCCollData *data = g_slice_new (EBCCollData);
1471 
1472 	data->book_cache = book_cache;
1473 	data->field = field;
1474 
1475 	return data;
1476 }
1477 
1478 static void
ebc_coll_data_free(EBCCollData * data)1479 ebc_coll_data_free (EBCCollData *data)
1480 {
1481 	if (data)
1482 		g_slice_free (EBCCollData, data);
1483 }
1484 
1485 /* COLLATE functions are generated on demand only */
1486 static void
ebc_generate_collator(gpointer ref,sqlite3 * db,gint eTextRep,const gchar * coll_name)1487 ebc_generate_collator (gpointer ref,
1488 		       sqlite3 *db,
1489 		       gint eTextRep,
1490 		       const gchar *coll_name)
1491 {
1492 	EBookCache *book_cache = ref;
1493 	EBCCollData *data;
1494 	EContactField field;
1495 	const gchar *field_name;
1496 
1497 	field_name = coll_name + strlen (EBC_COLLATE_PREFIX);
1498 	field = e_contact_field_id (field_name);
1499 
1500 	/* This should be caught before reaching here, just an extra check */
1501 	if (field == 0 || field >= E_CONTACT_FIELD_LAST ||
1502 	    e_contact_field_type (field) != G_TYPE_STRING) {
1503 		g_warning ("Specified collation on invalid contact field");
1504 		return;
1505 	}
1506 
1507 	data = ebc_coll_data_new (book_cache, field);
1508 	sqlite3_create_collation_v2 (
1509 		db, coll_name, SQLITE_UTF8,
1510 		data, ebc_fallback_collator,
1511 		(GDestroyNotify) ebc_coll_data_free);
1512 }
1513 
1514 /***************************************************************
1515  * Structures and utilities for preflight and query generation *
1516  ***************************************************************/
1517 
1518 /* This enumeration is ordered by severity, higher values
1519  * of PreflightStatus take precedence in error reporting.
1520  */
1521 typedef enum {
1522 	PREFLIGHT_OK = 0,
1523 	PREFLIGHT_LIST_ALL,
1524 	PREFLIGHT_NOT_SUMMARIZED,
1525 	PREFLIGHT_INVALID,
1526 	PREFLIGHT_UNSUPPORTED,
1527 } PreflightStatus;
1528 
1529 /* Whether we can satisfy the constraints or whether we
1530  * need to do a fallback, we still need to call
1531  * ebc_generate_constraints()
1532  */
1533 #define EBC_STATUS_GEN_CONSTRAINTS(status) \
1534 	((status) == PREFLIGHT_OK || \
1535 	 (status) == PREFLIGHT_NOT_SUMMARIZED)
1536 
1537 /* Internal extension of the EBookQueryTest enumeration */
1538 enum {
1539 	/* 'exists' is a supported query on a field, but not part of EBookQueryTest */
1540 	BOOK_QUERY_EXISTS = E_BOOK_QUERY_LAST,
1541 	BOOK_QUERY_EXISTS_VCARD,
1542 
1543 	/* From here the compound types start */
1544 	BOOK_QUERY_SUB_AND,
1545 	BOOK_QUERY_SUB_OR,
1546 	BOOK_QUERY_SUB_NOT,
1547 	BOOK_QUERY_SUB_END,
1548 
1549 	BOOK_QUERY_SUB_FIRST = BOOK_QUERY_SUB_AND,
1550 };
1551 
1552 #define EBC_QUERY_TYPE_STR(query) \
1553 	((query) == BOOK_QUERY_EXISTS ? "exists" : \
1554 	 (query) == BOOK_QUERY_EXISTS_VCARD ? "exists_vcard" : \
1555 	 (query) == BOOK_QUERY_SUB_AND ? "AND" : \
1556 	 (query) == BOOK_QUERY_SUB_OR ? "OR" : \
1557 	 (query) == BOOK_QUERY_SUB_NOT ? "NOT" : \
1558 	 (query) == BOOK_QUERY_SUB_END ? "END" : \
1559 	 (query) == E_BOOK_QUERY_IS ? "is" : \
1560 	 (query) == E_BOOK_QUERY_CONTAINS ? "contains" : \
1561 	 (query) == E_BOOK_QUERY_BEGINS_WITH ? "begins-with" : \
1562 	 (query) == E_BOOK_QUERY_ENDS_WITH ? "ends-with" : \
1563 	 (query) == E_BOOK_QUERY_EQUALS_PHONE_NUMBER ? "eqphone" : \
1564 	 (query) == E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER ? "eqphone-national" : \
1565 	 (query) == E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER ? "eqphone-short" : \
1566 	 (query) == E_BOOK_QUERY_REGEX_NORMAL ? "regex-normal" : \
1567 	 (query) == E_BOOK_QUERY_REGEX_NORMAL ? "regex-raw" : "(unknown)")
1568 
1569 #define EBC_FIELD_ID_STR(field_id) \
1570 	((field_id) == E_CONTACT_FIELD_LAST ? "x-evolution-any-field" : \
1571 	 (field_id) == 0 ? "(not an EContactField)" : \
1572 	 e_contact_field_name (field_id))
1573 
1574 #define IS_QUERY_PHONE(query) \
1575 	((query) == E_BOOK_QUERY_EQUALS_PHONE_NUMBER || \
1576 	 (query) == E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER || \
1577 	 (query) == E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER)
1578 
1579 typedef struct {
1580 	guint          query; /* EBookQueryTest (extended) */
1581 } QueryElement;
1582 
1583 typedef struct {
1584 	guint          query; /* EBookQueryTest (extended) */
1585 } QueryDelimiter;
1586 
1587 typedef struct {
1588 	guint          query;          /* EBookQueryTest (extended) */
1589 
1590 	EContactField  field_id;       /* The EContactField to compare */
1591 	SummaryField  *field;          /* The summary field for 'field' */
1592 	gchar         *value;          /* The value to compare with */
1593 
1594 } QueryFieldTest;
1595 
1596 typedef struct {
1597 	guint          query;          /* EBookQueryTest (extended) */
1598 
1599 	/* Common fields from QueryFieldTest */
1600 	EContactField  field_id;       /* The EContactField to compare */
1601 	SummaryField  *field;          /* The summary field for 'field' */
1602 	gchar         *value;          /* The value to compare with */
1603 
1604 	/* Extension */
1605 	gchar         *region;   /* Region code from the query input */
1606 	gchar         *national; /* Parsed national number */
1607 	gint           country;  /* Parsed country code */
1608 } QueryPhoneTest;
1609 
1610 /* Stack initializer for the PreflightContext struct below */
1611 #define PREFLIGHT_CONTEXT_INIT { PREFLIGHT_OK, NULL, 0, FALSE }
1612 
1613 typedef struct {
1614 	PreflightStatus  status;         /* result status */
1615 	GPtrArray       *constraints;    /* main query; may be NULL */
1616 	guint64          aux_mask;       /* Bitmask of which auxiliary tables are needed in the query */
1617 	guint64          left_join_mask; /* Do we need to use a LEFT JOIN */
1618 } PreflightContext;
1619 
1620 static QueryElement *
query_delimiter_new(guint query)1621 query_delimiter_new (guint query)
1622 {
1623 	QueryDelimiter *delim;
1624 
1625 	g_return_val_if_fail (query >= BOOK_QUERY_SUB_FIRST, NULL);
1626 
1627 	delim = g_slice_new (QueryDelimiter);
1628 	delim->query = query;
1629 
1630 	return (QueryElement *) delim;
1631 }
1632 
1633 static QueryFieldTest *
query_field_test_new(guint query,EContactField field)1634 query_field_test_new (guint query,
1635 		      EContactField field)
1636 {
1637 	QueryFieldTest *test;
1638 
1639 	g_return_val_if_fail (query < BOOK_QUERY_SUB_FIRST, NULL);
1640 	g_return_val_if_fail (IS_QUERY_PHONE (query) == FALSE, NULL);
1641 
1642 	test = g_slice_new (QueryFieldTest);
1643 	test->query = query;
1644 	test->field_id = field;
1645 
1646 	/* Instead of g_slice_new0, NULL them out manually */
1647 	test->field = NULL;
1648 	test->value = NULL;
1649 
1650 	return test;
1651 }
1652 
1653 static QueryPhoneTest *
query_phone_test_new(guint query,EContactField field)1654 query_phone_test_new (guint query,
1655 		      EContactField field)
1656 {
1657 	QueryPhoneTest *test;
1658 
1659 	g_return_val_if_fail (IS_QUERY_PHONE (query), NULL);
1660 
1661 	test = g_slice_new (QueryPhoneTest);
1662 	test->query = query;
1663 	test->field_id = field;
1664 
1665 	/* Instead of g_slice_new0, NULL them out manually */
1666 	test->field = NULL;
1667 	test->value = NULL;
1668 
1669 	/* Extra QueryPhoneTest fields */
1670 	test->region = NULL;
1671 	test->national = NULL;
1672 	test->country = 0;
1673 
1674 	return test;
1675 }
1676 
1677 static void
query_element_free(QueryElement * element)1678 query_element_free (QueryElement *element)
1679 {
1680 	if (element) {
1681 
1682 		if (element->query >= BOOK_QUERY_SUB_FIRST) {
1683 			QueryDelimiter *delim = (QueryDelimiter *) element;
1684 
1685 			g_slice_free (QueryDelimiter, delim);
1686 		} else if (IS_QUERY_PHONE (element->query)) {
1687 			QueryPhoneTest *test = (QueryPhoneTest *) element;
1688 
1689 			g_free (test->value);
1690 			g_free (test->region);
1691 			g_free (test->national);
1692 			g_slice_free (QueryPhoneTest, test);
1693 		} else {
1694 			QueryFieldTest *test = (QueryFieldTest *) element;
1695 
1696 			g_free (test->value);
1697 			g_slice_free (QueryFieldTest, test);
1698 		}
1699 	}
1700 }
1701 
1702 /* We use ptr arrays for the QueryElement vectors */
1703 static inline void
constraints_insert(GPtrArray * array,gint idx,gpointer data)1704 constraints_insert (GPtrArray *array,
1705 		    gint idx,
1706 		    gpointer data)
1707 {
1708 	g_return_if_fail ((idx >= -1) && (idx < (gint) array->len + 1));
1709 
1710 	if (idx < 0)
1711 		idx = array->len;
1712 
1713 	g_ptr_array_add (array, NULL);
1714 
1715 	if (idx != (array->len - 1))
1716 		memmove (
1717 			&(array->pdata[idx + 1]),
1718 			&(array->pdata[idx]),
1719 			((array->len - 1) - idx) * sizeof (gpointer));
1720 
1721 	array->pdata[idx] = data;
1722 }
1723 
1724 static inline void
constraints_insert_delimiter(GPtrArray * array,gint idx,guint query)1725 constraints_insert_delimiter (GPtrArray *array,
1726 			      gint idx,
1727 			      guint query)
1728 {
1729 	QueryElement *delim;
1730 
1731 	delim = query_delimiter_new (query);
1732 	constraints_insert (array, idx, delim);
1733 }
1734 
1735 static inline void
constraints_insert_field_test(GPtrArray * array,gint idx,SummaryField * field,guint query,const gchar * value)1736 constraints_insert_field_test (GPtrArray *array,
1737 			       gint idx,
1738 			       SummaryField *field,
1739 			       guint query,
1740 			       const gchar *value)
1741 {
1742 	QueryFieldTest *test;
1743 
1744 	test = query_field_test_new (query, field->field_id);
1745 	test->field = field;
1746 	test->value = g_strdup (value);
1747 
1748 	constraints_insert (array, idx, test);
1749 }
1750 
1751 static void
preflight_context_clear(PreflightContext * context)1752 preflight_context_clear (PreflightContext *context)
1753 {
1754 	if (context) {
1755 		/* Free any allocated data, but leave the context values in place */
1756 		if (context->constraints)
1757 			g_ptr_array_free (context->constraints, TRUE);
1758 		context->constraints = NULL;
1759 	}
1760 }
1761 
1762 /* A small API to track the current sub-query context.
1763  *
1764  * I.e. sub contexts can be OR, AND, or NOT, in which
1765  * field tests or other sub contexts are nested.
1766  *
1767  * The 'count' field is a simple counter of how deep the contexts are nested.
1768  *
1769  * The 'cond_count' field is to be used by the caller for its own purposes;
1770  * it is incremented in sub_query_context_push() only if the inc_cond_count
1771  * parameter is TRUE. This is used by query_preflight_check() in a complex
1772  * fashion which is described there.
1773  */
1774 typedef GQueue SubQueryContext;
1775 
1776 typedef struct {
1777 	guint sub_type; /* The type of this sub context */
1778 	guint count;    /* The number of field tests so far in this context */
1779 	guint cond_count; /* User-specific conditional counter */
1780 } SubQueryData;
1781 
1782 #define sub_query_context_new g_queue_new
1783 #define sub_query_context_free(ctx) g_queue_free (ctx)
1784 
1785 static inline void
sub_query_context_push(SubQueryContext * ctx,guint sub_type,gboolean inc_cond_count)1786 sub_query_context_push (SubQueryContext *ctx,
1787 			guint sub_type,
1788 			gboolean inc_cond_count)
1789 {
1790 	SubQueryData *data, *prev;
1791 
1792 	prev = g_queue_peek_tail (ctx);
1793 
1794 	data = g_slice_new (SubQueryData);
1795 	data->sub_type = sub_type;
1796 	data->count = 0;
1797 	data->cond_count = prev ? prev->cond_count : 0;
1798 	if (inc_cond_count)
1799 		data->cond_count++;
1800 
1801 	g_queue_push_tail (ctx, data);
1802 }
1803 
1804 static inline void
sub_query_context_pop(SubQueryContext * ctx)1805 sub_query_context_pop (SubQueryContext *ctx)
1806 {
1807 	SubQueryData *data;
1808 
1809 	data = g_queue_pop_tail (ctx);
1810 	g_slice_free (SubQueryData, data);
1811 }
1812 
1813 static inline guint
sub_query_context_peek_type(SubQueryContext * ctx)1814 sub_query_context_peek_type (SubQueryContext *ctx)
1815 {
1816 	SubQueryData *data;
1817 
1818 	data = g_queue_peek_tail (ctx);
1819 
1820 	return data->sub_type;
1821 }
1822 
1823 static inline guint
sub_query_context_peek_cond_counter(SubQueryContext * ctx)1824 sub_query_context_peek_cond_counter (SubQueryContext *ctx)
1825 {
1826 	SubQueryData *data;
1827 
1828 	data = g_queue_peek_tail (ctx);
1829 
1830 	if (data)
1831 		return data->cond_count;
1832 	else
1833 		return 0;
1834 }
1835 
1836 /* Returns the context field test count before incrementing */
1837 static inline guint
sub_query_context_increment(SubQueryContext * ctx)1838 sub_query_context_increment (SubQueryContext *ctx)
1839 {
1840 	SubQueryData *data;
1841 
1842 	data = g_queue_peek_tail (ctx);
1843 
1844 	if (data) {
1845 		data->count++;
1846 
1847 		return (data->count - 1);
1848 	}
1849 
1850 	/* If we're not in a sub context, just return 0 */
1851 	return 0;
1852 }
1853 
1854 /**********************************************************
1855  *                  Querying preflighting                 *
1856  **********************************************************
1857  *
1858  * The preflight checks are performed before a query might
1859  * take place in order to evaluate whether the given query
1860  * can be performed with the current summary configuration.
1861  *
1862  * After preflighting, all relevant data has been extracted
1863  * from the search expression and the search expression need
1864  * not be parsed again.
1865  */
1866 
1867 /* The PreflightSubCallback is expected to return TRUE
1868  * to keep iterating and FALSE to abort iteration.
1869  *
1870  * The sub_level is the counter of how deep the 'element'
1871  * is nested in sub elements, the offset is the real offset
1872  * of 'element' in the array passed to query_preflight_foreach_sub().
1873  */
1874 typedef gboolean (* PreflightSubCallback) (QueryElement *element,
1875 					   gint          sub_level,
1876 					   gint          offset,
1877 					   gpointer      user_data);
1878 
1879 static void
query_preflight_foreach_sub(QueryElement ** elements,gint n_elements,gint offset,gboolean include_delim,PreflightSubCallback callback,gpointer user_data)1880 query_preflight_foreach_sub (QueryElement **elements,
1881 			     gint n_elements,
1882 			     gint offset,
1883 			     gboolean include_delim,
1884 			     PreflightSubCallback callback,
1885 			     gpointer user_data)
1886 {
1887 	gint sub_counter = 1, ii;
1888 
1889 	g_return_if_fail (offset >= 0 && offset < n_elements);
1890 	g_return_if_fail (elements[offset]->query >= BOOK_QUERY_SUB_FIRST);
1891 	g_return_if_fail (callback != NULL);
1892 
1893 	if (include_delim && !callback (elements[offset], 0, offset, user_data))
1894 		return;
1895 
1896 	for (ii = (offset + 1); sub_counter > 0 && ii < n_elements; ii++) {
1897 
1898 		if (elements[ii]->query >= BOOK_QUERY_SUB_FIRST) {
1899 
1900 			if (elements[ii]->query == BOOK_QUERY_SUB_END)
1901 				sub_counter--;
1902 			else
1903 				sub_counter++;
1904 
1905 			if (include_delim &&
1906 			    !callback (elements[ii], sub_counter, ii, user_data))
1907 				break;
1908 		} else {
1909 
1910 			if (!callback (elements[ii], sub_counter, ii, user_data))
1911 				break;
1912 		}
1913 	}
1914 }
1915 
1916 /* Table used in ESExp parsing below */
1917 static const struct {
1918 	const gchar *name;    /* Name of the symbol to match for this parse phase */
1919 	gboolean     subset;  /* TRUE for the subset ESExpIFunc, otherwise the field check ESExpFunc */
1920 	guint        test;    /* Extended EBookQueryTest value */
1921 } check_symbols[] = {
1922 	{ "and",              TRUE, BOOK_QUERY_SUB_AND },
1923 	{ "or",               TRUE, BOOK_QUERY_SUB_OR },
1924 	{ "not",              TRUE, BOOK_QUERY_SUB_NOT },
1925 
1926 	{ "contains",         FALSE, E_BOOK_QUERY_CONTAINS },
1927 	{ "is",               FALSE, E_BOOK_QUERY_IS },
1928 	{ "beginswith",       FALSE, E_BOOK_QUERY_BEGINS_WITH },
1929 	{ "endswith",         FALSE, E_BOOK_QUERY_ENDS_WITH },
1930 	{ "eqphone",          FALSE, E_BOOK_QUERY_EQUALS_PHONE_NUMBER },
1931 	{ "eqphone_national", FALSE, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER },
1932 	{ "eqphone_short",    FALSE, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER },
1933 	{ "regex_normal",     FALSE, E_BOOK_QUERY_REGEX_NORMAL },
1934 	{ "regex_raw",        FALSE, E_BOOK_QUERY_REGEX_RAW },
1935 	{ "exists",           FALSE, BOOK_QUERY_EXISTS },
1936 	{ "exists_vcard",     FALSE, BOOK_QUERY_EXISTS_VCARD }
1937 };
1938 
1939 /* Cheat our way into passing mode data to these funcs */
1940 static ESExpResult *
func_check_subset(ESExp * f,gint argc,struct _ESExpTerm ** argv,gpointer data)1941 func_check_subset (ESExp *f,
1942 		   gint argc,
1943 		   struct _ESExpTerm **argv,
1944 		   gpointer data)
1945 {
1946 	ESExpResult *result, *sub_result;
1947 	GPtrArray *result_array;
1948 	QueryElement *element, **sub_elements;
1949 	gint ii, jj, len;
1950 	guint query_type;
1951 
1952 	query_type = GPOINTER_TO_UINT (data);
1953 
1954 	/* The compound query delimiter is the first element in this return array */
1955 	result_array = g_ptr_array_new_with_free_func ((GDestroyNotify) query_element_free);
1956 	element = query_delimiter_new (query_type);
1957 	g_ptr_array_add (result_array, element);
1958 
1959 	for (ii = 0; ii < argc; ii++) {
1960 		sub_result = e_sexp_term_eval (f, argv[ii]);
1961 
1962 		if (sub_result->type == ESEXP_RES_ARRAY_PTR) {
1963 			/* Steal the elements directly from the sub result */
1964 			sub_elements = (QueryElement **) sub_result->value.ptrarray->pdata;
1965 			len = sub_result->value.ptrarray->len;
1966 
1967 			for (jj = 0; jj < len; jj++) {
1968 				element = sub_elements[jj];
1969 				sub_elements[jj] = NULL;
1970 
1971 				g_ptr_array_add (result_array, element);
1972 			}
1973 		}
1974 		e_sexp_result_free (f, sub_result);
1975 	}
1976 
1977 	/* The last element in this return array is the sub end delimiter */
1978 	element = query_delimiter_new (BOOK_QUERY_SUB_END);
1979 	g_ptr_array_add (result_array, element);
1980 
1981 	result = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1982 	result->value.ptrarray = result_array;
1983 
1984 	return result;
1985 }
1986 
1987 static ESExpResult *
func_check(struct _ESExp * f,gint argc,struct _ESExpResult ** argv,gpointer data)1988 func_check (struct _ESExp *f,
1989 	    gint argc,
1990 	    struct _ESExpResult **argv,
1991 	    gpointer data)
1992 {
1993 	ESExpResult *result;
1994 	GPtrArray *result_array;
1995 	QueryElement *element = NULL;
1996 	EContactField field_id = 0;
1997 	const gchar *query_name = NULL;
1998 	const gchar *query_value = NULL;
1999 	const gchar *query_extra = NULL;
2000 	guint query_type;
2001 
2002 	query_type = GPOINTER_TO_UINT (data);
2003 
2004 	if (argc == 1 && query_type == BOOK_QUERY_EXISTS &&
2005 	    argv[0]->type == ESEXP_RES_STRING) {
2006 		query_name = argv[0]->value.string;
2007 
2008 		field_id = e_contact_field_id (query_name);
2009 	} else if (argc == 2 &&
2010 	    argv[0]->type == ESEXP_RES_STRING &&
2011 	    argv[1]->type == ESEXP_RES_STRING) {
2012 		query_name = argv[0]->value.string;
2013 		query_value = argv[1]->value.string;
2014 
2015 		/* We use E_CONTACT_FIELD_LAST to hold the special case of "x-evolution-any-field" */
2016 		if (g_strcmp0 (query_name, "x-evolution-any-field") == 0)
2017 			field_id = E_CONTACT_FIELD_LAST;
2018 		else
2019 			field_id = e_contact_field_id (query_name);
2020 
2021 	} else if (argc == 3 &&
2022 		   argv[0]->type == ESEXP_RES_STRING &&
2023 		   argv[1]->type == ESEXP_RES_STRING &&
2024 		   argv[2]->type == ESEXP_RES_STRING) {
2025 		query_name = argv[0]->value.string;
2026 		query_value = argv[1]->value.string;
2027 		query_extra = argv[2]->value.string;
2028 
2029 		field_id = e_contact_field_id (query_name);
2030 	}
2031 
2032 	if (IS_QUERY_PHONE (query_type)) {
2033 		QueryPhoneTest *test;
2034 
2035 		/* Collect data from this field test */
2036 		test = query_phone_test_new (query_type, field_id);
2037 		test->value = g_strdup (query_value);
2038 		test->region = g_strdup (query_extra);
2039 
2040 		element = (QueryElement *) test;
2041 	} else {
2042 		QueryFieldTest *test;
2043 
2044 		/* Collect data from this field test */
2045 		test = query_field_test_new (query_type, field_id);
2046 		test->value = g_strdup (query_value);
2047 
2048 		element = (QueryElement *) test;
2049 	}
2050 
2051 	/* Return an array with only one element, for lack of a pointer type ESExpResult */
2052 	result_array = g_ptr_array_new_with_free_func ((GDestroyNotify) query_element_free);
2053 	g_ptr_array_add (result_array, element);
2054 
2055 	result = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
2056 	result->value.ptrarray = result_array;
2057 
2058 	return result;
2059 }
2060 
2061 /* Initial stage of preflighting:
2062  *
2063  *  o Parse the search expression and generate our array of QueryElements
2064  *  o Collect lengths of query terms
2065  */
2066 static void
query_preflight_initialize(PreflightContext * context,const gchar * sexp)2067 query_preflight_initialize (PreflightContext *context,
2068 			    const gchar *sexp)
2069 {
2070 	ESExp *sexp_parser;
2071 	ESExpResult *result;
2072 	gint esexp_error, ii;
2073 
2074 	if (!sexp || !*sexp || g_strcmp0 (sexp, "#t") == 0) {
2075 		context->status = PREFLIGHT_LIST_ALL;
2076 		return;
2077 	}
2078 
2079 	sexp_parser = e_sexp_new ();
2080 
2081 	for (ii = 0; ii < G_N_ELEMENTS (check_symbols); ii++) {
2082 		if (check_symbols[ii].subset) {
2083 			e_sexp_add_ifunction (
2084 				sexp_parser, 0, check_symbols[ii].name,
2085 				func_check_subset,
2086 				GUINT_TO_POINTER (check_symbols[ii].test));
2087 		} else {
2088 			e_sexp_add_function (
2089 				sexp_parser, 0, check_symbols[ii].name,
2090 				func_check,
2091 				GUINT_TO_POINTER (check_symbols[ii].test));
2092 		}
2093 	}
2094 
2095 	e_sexp_input_text (sexp_parser, sexp, strlen (sexp));
2096 	esexp_error = e_sexp_parse (sexp_parser);
2097 
2098 	if (esexp_error == -1) {
2099 		context->status = PREFLIGHT_INVALID;
2100 	} else {
2101 		result = e_sexp_eval (sexp_parser);
2102 		if (result) {
2103 			if (result->type == ESEXP_RES_ARRAY_PTR) {
2104 				/* Just steal the array away from the ESexpResult */
2105 				context->constraints = result->value.ptrarray;
2106 				result->value.ptrarray = NULL;
2107 			} else {
2108 				context->status = PREFLIGHT_INVALID;
2109 			}
2110 		}
2111 
2112 		e_sexp_result_free (sexp_parser, result);
2113 	}
2114 
2115 	g_object_unref (sexp_parser);
2116 }
2117 
2118 typedef struct {
2119 	EBookCache *book_cache;
2120 	SummaryField *field;
2121 	gboolean condition;
2122 } AttrListCheckData;
2123 
2124 static gboolean
check_has_attr_list_cb(QueryElement * element,gint sub_level,gint offset,gpointer user_data)2125 check_has_attr_list_cb (QueryElement *element,
2126 			gint sub_level,
2127 			gint offset,
2128 			gpointer user_data)
2129 {
2130 	QueryFieldTest *test = (QueryFieldTest *) element;
2131 	AttrListCheckData *data = (AttrListCheckData *) user_data;
2132 
2133 	/* We havent resolved all the fields at this stage yet */
2134 	if (!test->field)
2135 		test->field = summary_field_get (data->book_cache, test->field_id);
2136 
2137 	if (test->field && test->field->type == E_TYPE_CONTACT_ATTR_LIST)
2138 		data->condition = TRUE;
2139 
2140 	/* Keep looping until we find one */
2141 	return !data->condition;
2142 }
2143 
2144 static gboolean
check_different_fields_cb(QueryElement * element,gint sub_level,gint offset,gpointer user_data)2145 check_different_fields_cb (QueryElement *element,
2146 			   gint sub_level,
2147 			   gint offset,
2148 			   gpointer user_data)
2149 {
2150 	QueryFieldTest *test = (QueryFieldTest *) element;
2151 	AttrListCheckData *data = (AttrListCheckData *) user_data;
2152 
2153 	/* We havent resolved all the fields at this stage yet */
2154 	if (!test->field)
2155 		test->field = summary_field_get (data->book_cache, test->field_id);
2156 
2157 	if (test->field && data->field && test->field != data->field)
2158 		data->condition = TRUE;
2159 	else
2160 		data->field = test->field;
2161 
2162 	/* Keep looping until we find one */
2163 	return !data->condition;
2164 }
2165 
2166 /* What is done in this pass:
2167  *  o Viability of the query is analyzed, i.e. can it be done with the summary columns.
2168  *  o Phone numbers are parsed and loaded onto QueryPhoneTests
2169  *  o Bitmask of auxiliary tables is collected
2170  */
2171 static void
query_preflight_check(PreflightContext * context,EBookCache * book_cache)2172 query_preflight_check (PreflightContext *context,
2173 		       EBookCache *book_cache)
2174 {
2175 	gint ii, n_elements;
2176 	QueryElement **elements;
2177 	SubQueryContext *ctx;
2178 
2179 	context->status = PREFLIGHT_OK;
2180 
2181 	if (context->constraints != NULL) {
2182 		elements = (QueryElement **) context->constraints->pdata;
2183 		n_elements = context->constraints->len;
2184 	} else {
2185 		elements = NULL;
2186 		n_elements = 0;
2187 	}
2188 
2189 	ctx = sub_query_context_new ();
2190 
2191 	for (ii = 0; ii < n_elements; ii++) {
2192 		QueryFieldTest *test;
2193 		guint field_test;
2194 
2195 		if (elements[ii]->query >= BOOK_QUERY_SUB_FIRST) {
2196 			AttrListCheckData data = { book_cache, NULL, FALSE };
2197 
2198 			switch (elements[ii]->query) {
2199 			case BOOK_QUERY_SUB_OR:
2200 				/* An OR doesn't have to force us to use a LEFT JOIN, as long
2201 				   as all its sub-conditions are on the same field. */
2202 				query_preflight_foreach_sub (elements,
2203 							     n_elements,
2204 							     ii, FALSE,
2205 							     check_different_fields_cb,
2206 							     &data);
2207 				/* falls through */
2208 			case BOOK_QUERY_SUB_AND:
2209 				sub_query_context_push (ctx, elements[ii]->query, data.condition);
2210 				break;
2211 			case BOOK_QUERY_SUB_END:
2212 				sub_query_context_pop (ctx);
2213 				break;
2214 
2215 			/* It's too complicated to properly perform
2216 			 * the unary NOT operator on a constraint which
2217 			 * accesses attribute lists.
2218 			 *
2219 			 * Hint, if the contact has a "%.com" email address
2220 			 * and a "%.org" email address, what do we return
2221 			 * for (not (endswith "email" ".com") ?
2222 			 *
2223 			 * Currently we rely on DISTINCT to sort out
2224 			 * muliple results from the attribute list tables,
2225 			 * this breaks down with NOT.
2226 			 */
2227 			case BOOK_QUERY_SUB_NOT:
2228 				query_preflight_foreach_sub (elements,
2229 							     n_elements,
2230 							     ii, FALSE,
2231 							     check_has_attr_list_cb,
2232 							     &data);
2233 
2234 				if (data.condition) {
2235 					context->status = MAX (
2236 						context->status,
2237 						PREFLIGHT_NOT_SUMMARIZED);
2238 				}
2239 				break;
2240 
2241 			default:
2242 				g_warn_if_reached ();
2243 			}
2244 
2245 			continue;
2246 		}
2247 
2248 		test = (QueryFieldTest *) elements[ii];
2249 		field_test = (EBookQueryTest) test->query;
2250 
2251 		if (!test->field)
2252 			test->field = summary_field_get (book_cache, test->field_id);
2253 
2254 		/* Even if the field is not in the summary, we need to
2255 		 * retport unsupported errors if phone number queries are
2256 		 * issued while libphonenumber is unavailable
2257 		 */
2258 		if (!test->field) {
2259 			/* Special case for e_book_query_any_field_contains().
2260 			 *
2261 			 * We interpret 'x-evolution-any-field' as E_CONTACT_FIELD_LAST
2262 			 */
2263 			if (test->field_id == E_CONTACT_FIELD_LAST) {
2264 				/* If we search for a NULL or zero length string, it
2265 				 * means 'get all contacts', that is considered a summary
2266 				 * query but is handled differently (i.e. we just drop the
2267 				 * field tests and run a regular query).
2268 				 *
2269 				 * This is only true if the 'any field contains' query is
2270 				 * the only test in the constraints, however.
2271 				 */
2272 				if (n_elements == 1 && (!test->value || !test->value[0])) {
2273 
2274 					context->status = MAX (context->status, PREFLIGHT_LIST_ALL);
2275 				} else {
2276 
2277 					/* Searching for a value with 'x-evolution-any-field' is
2278 					 * not a summary query.
2279 					 */
2280 					context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2281 				}
2282 			} else {
2283 				/* Couldnt resolve the field, it's not a summary query */
2284 				context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2285 			}
2286 		}
2287 
2288 		if (test->field && test->field->type == E_TYPE_CONTACT_CERT) {
2289 			/* For certificates, and later potentially other fields,
2290 			 * the only information in the summary is the fact that
2291 			 * they exist, or not. So the only check we can do from
2292 			 * the summary is BOOK_QUERY_EXISTS. */
2293 			if (field_test != BOOK_QUERY_EXISTS) {
2294 				context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2295 			}
2296 			/* Bypass the other checks below which are not appropriate. */
2297 			continue;
2298 		}
2299 
2300 		switch (field_test) {
2301 		case E_BOOK_QUERY_IS:
2302 			break;
2303 
2304 		case BOOK_QUERY_EXISTS:
2305 		case E_BOOK_QUERY_CONTAINS:
2306 		case E_BOOK_QUERY_BEGINS_WITH:
2307 		case E_BOOK_QUERY_ENDS_WITH:
2308 		case E_BOOK_QUERY_REGEX_NORMAL:
2309 			/* All of these queries can only apply to string fields,
2310 			 * or fields which hold multiple strings
2311 			 */
2312 			if (test->field) {
2313 				if (test->field->type == G_TYPE_BOOLEAN &&
2314 				    field_test == BOOK_QUERY_EXISTS) {
2315 					context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2316 				} else if (test->field->type != G_TYPE_STRING &&
2317 					   test->field->type != E_TYPE_CONTACT_ATTR_LIST) {
2318 					context->status = MAX (context->status, PREFLIGHT_INVALID);
2319 				}
2320 			}
2321 
2322 			break;
2323 
2324 		case BOOK_QUERY_EXISTS_VCARD:
2325 			/* Exists vCard queries only supported in the fallback */
2326 			context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2327 			break;
2328 
2329 		case E_BOOK_QUERY_REGEX_RAW:
2330 			/* Raw regex queries only supported in the fallback */
2331 			context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
2332 			break;
2333 
2334 		case E_BOOK_QUERY_EQUALS_PHONE_NUMBER:
2335 		case E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER:
2336 		case E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER:
2337 			/* Phone number queries are supported so long as they are in the summary,
2338 			 * libphonenumber is available, and the phone number string is a valid one
2339 			 */
2340 			if (!e_phone_number_is_supported ()) {
2341 				context->status = MAX (context->status, PREFLIGHT_UNSUPPORTED);
2342 			} else {
2343 				QueryPhoneTest *phone_test = (QueryPhoneTest *) test;
2344 				EPhoneNumberCountrySource source;
2345 				EPhoneNumber *number;
2346 				const gchar *region_code;
2347 
2348 				if (phone_test->region)
2349 					region_code = phone_test->region;
2350 				else
2351 					region_code = book_cache->priv->region_code;
2352 
2353 				number = e_phone_number_from_string (
2354 					phone_test->value,
2355 					region_code, NULL);
2356 
2357 				if (number == NULL) {
2358 					context->status = MAX (context->status, PREFLIGHT_INVALID);
2359 				} else {
2360 					/* Collect values we'll need later while generating field
2361 					 * tests, no need to parse the phone number more than once
2362 					 */
2363 					phone_test->national = e_phone_number_get_national_number (number);
2364 					phone_test->country = e_phone_number_get_country_code (number, &source);
2365 					phone_test->national = remove_leading_zeros (phone_test->national);
2366 
2367 					if (source == E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT)
2368 						phone_test->country = 0;
2369 
2370 					e_phone_number_free (number);
2371 				}
2372 			}
2373 			break;
2374 		}
2375 
2376 		if (test->field &&
2377 		    test->field->type == E_TYPE_CONTACT_ATTR_LIST) {
2378 			gint aux_index = summary_field_get_index (book_cache, test->field_id);
2379 
2380 			/* It's really improbable that we ever get 64 fields in the summary
2381 			 * In any case we warn about this.
2382 			 */
2383 			g_warn_if_fail (aux_index >= 0 && aux_index < EBC_MAX_SUMMARY_FIELDS);
2384 
2385 			/* Just to mute a compiler warning when aux_index == -1 */
2386 			aux_index = ABS (aux_index);
2387 
2388 			context->aux_mask |= (1 << aux_index);
2389 
2390 			/* If this condition is a *requirement* for the overall query to
2391 			   match a given record (i.e. there's no surrounding 'OR' but
2392 			   only 'AND'), then we can use an inner join for the query and
2393 			   it will be a lot more efficient. If records without this
2394 			   condition can also match the overall condition, then we must
2395 			   use LEFT JOIN. */
2396 			if (sub_query_context_peek_cond_counter (ctx)) {
2397 				context->left_join_mask |= (1 << aux_index);
2398 			}
2399 		}
2400 	}
2401 
2402 	sub_query_context_free (ctx);
2403 }
2404 
2405 /* Handle special case of E_CONTACT_FULL_NAME
2406  *
2407  * For any query which accesses the full name field,
2408  * we need to also OR it with any of the related name
2409  * fields, IF those are found in the summary as well.
2410  */
2411 static void
query_preflight_substitute_full_name(PreflightContext * context,EBookCache * book_cache)2412 query_preflight_substitute_full_name (PreflightContext *context,
2413 				      EBookCache *book_cache)
2414 {
2415 	gint ii, jj;
2416 
2417 	for (ii = 0; context->constraints != NULL && ii < context->constraints->len; ii++) {
2418 		SummaryField *family_name, *given_name, *nickname;
2419 		QueryElement *element;
2420 		QueryFieldTest *test;
2421 
2422 		element = g_ptr_array_index (context->constraints, ii);
2423 
2424 		if (element->query >= BOOK_QUERY_SUB_FIRST)
2425 			continue;
2426 
2427 		test = (QueryFieldTest *) element;
2428 		if (test->field_id != E_CONTACT_FULL_NAME)
2429 			continue;
2430 
2431 		family_name = summary_field_get (book_cache, E_CONTACT_FAMILY_NAME);
2432 		given_name = summary_field_get (book_cache, E_CONTACT_GIVEN_NAME);
2433 		nickname = summary_field_get (book_cache, E_CONTACT_NICKNAME);
2434 
2435 		/* If any of these are in the summary, then we'll construct
2436 		 * a grouped OR statment for this E_CONTACT_FULL_NAME test */
2437 		if (family_name || given_name || nickname) {
2438 			/* Add the OR directly before the E_CONTACT_FULL_NAME test */
2439 			constraints_insert_delimiter (context->constraints, ii, BOOK_QUERY_SUB_OR);
2440 
2441 			jj = ii + 2;
2442 
2443 			if (family_name)
2444 				constraints_insert_field_test (
2445 					context->constraints, jj++,
2446 					family_name, test->query,
2447 					test->value);
2448 
2449 			if (given_name)
2450 				constraints_insert_field_test (
2451 					context->constraints, jj++,
2452 					given_name, test->query,
2453 					test->value);
2454 
2455 			if (nickname)
2456 				constraints_insert_field_test (
2457 					context->constraints, jj++,
2458 					nickname, test->query,
2459 					test->value);
2460 
2461 			constraints_insert_delimiter (context->constraints, jj, BOOK_QUERY_SUB_END);
2462 
2463 			ii = jj;
2464 		}
2465 	}
2466 }
2467 
2468 static void
query_preflight(PreflightContext * context,EBookCache * book_cache,const gchar * sexp)2469 query_preflight (PreflightContext *context,
2470 		 EBookCache *book_cache,
2471 		 const gchar *sexp)
2472 {
2473 	query_preflight_initialize (context, sexp);
2474 
2475 	if (context->status == PREFLIGHT_OK) {
2476 		query_preflight_check (context, book_cache);
2477 
2478 		/* No need to change the constraints if we're not
2479 		 * going to generate statements with it
2480 		 */
2481 		if (context->status == PREFLIGHT_OK) {
2482 			/* Handle E_CONTACT_FULL_NAME substitutions */
2483 			query_preflight_substitute_full_name (context, book_cache);
2484 		} else {
2485 			/* We might use this context to perform a fallback query,
2486 			 * so let's clear out all the constraints now
2487 			 */
2488 			preflight_context_clear (context);
2489 		}
2490 	}
2491 }
2492 
2493 /**********************************************************
2494  *                 Field Test Generators                  *
2495  **********************************************************
2496  *
2497  * This section contains the field test generators for
2498  * various EBookQueryTest types. When implementing new
2499  * query types, a new GenerateFieldTest needs to be created
2500  * and added to the table below.
2501  */
2502 
2503 typedef void (* GenerateFieldTest) (EBookCache *book_cache,
2504 				    GString *string,
2505 				    QueryFieldTest *test);
2506 
2507 /* Appends an identifier suitable to identify the
2508  * column to test in the context of a query.
2509  *
2510  * The suffix is for special indexed columns (such as
2511  * reverse values, sort keys, phone numbers, etc).
2512  */
2513 static void
ebc_string_append_column(GString * string,SummaryField * field,const gchar * suffix)2514 ebc_string_append_column (GString *string,
2515 			  SummaryField *field,
2516 			  const gchar *suffix)
2517 {
2518 	if (field->aux_table) {
2519 		g_string_append (string, field->aux_table_symbolic);
2520 		g_string_append (string, ".value");
2521 	} else {
2522 		g_string_append (string, "summary.");
2523 		g_string_append (string, field->dbname);
2524 	}
2525 
2526 	if (suffix) {
2527 		g_string_append_c (string, '_');
2528 		g_string_append (string, suffix);
2529 	}
2530 }
2531 
2532 /* This function escapes characters which need escaping
2533  * for LIKE statements as well as the single quotes.
2534  *
2535  * The return value is not suitable to be formatted
2536  * with %Q or %q
2537  */
2538 static gchar *
ebc_normalize_for_like(QueryFieldTest * test,gboolean reverse_string,gboolean * escape_needed)2539 ebc_normalize_for_like (QueryFieldTest *test,
2540 			gboolean reverse_string,
2541 			gboolean *escape_needed)
2542 {
2543 	GString *str;
2544 	size_t len;
2545 	gchar cc;
2546 	gboolean escape_modifier_needed = FALSE;
2547 	const gchar *normal = NULL;
2548 	const gchar *ptr;
2549 	const gchar *str_to_escape;
2550 	gchar *reverse = NULL;
2551 	gchar *freeme = NULL;
2552 
2553 	if (test->field_id == E_CONTACT_UID ||
2554 	    test->field_id == E_CONTACT_REV) {
2555 		normal = test->value;
2556 	} else {
2557 		freeme = e_util_utf8_normalize (test->value);
2558 		normal = freeme;
2559 	}
2560 
2561 	if (reverse_string) {
2562 		reverse = g_utf8_strreverse (normal, -1);
2563 		str_to_escape = reverse;
2564 	} else
2565 		str_to_escape = normal;
2566 
2567 	/* Just assume each character must be escaped. The result of this function
2568 	 * is discarded shortly after calling this function. Therefore it's
2569 	 * acceptable to possibly allocate twice the memory needed.
2570 	 */
2571 	len = strlen (str_to_escape);
2572 	str = g_string_sized_new (2 * len + 4 + strlen (EBC_ESCAPE_SEQUENCE) - 1);
2573 
2574 	ptr = str_to_escape;
2575 	while ((cc = *ptr++)) {
2576 		if (cc == '\'') {
2577 			g_string_append_c (str, '\'');
2578 		} else if (cc == '%' || cc == '_' || cc == '^') {
2579 			g_string_append_c (str, '^');
2580 			escape_modifier_needed = TRUE;
2581 		}
2582 
2583 		g_string_append_c (str, cc);
2584 	}
2585 
2586 	if (escape_needed)
2587 		*escape_needed = escape_modifier_needed;
2588 
2589 	g_free (freeme);
2590 	g_free (reverse);
2591 
2592 	return g_string_free (str, FALSE);
2593 }
2594 
2595 static void
field_test_query_is(EBookCache * book_cache,GString * string,QueryFieldTest * test)2596 field_test_query_is (EBookCache *book_cache,
2597 		     GString *string,
2598 		     QueryFieldTest *test)
2599 {
2600 	SummaryField *field = test->field;
2601 	gchar *normal;
2602 
2603 	ebc_string_append_column (string, field, NULL);
2604 
2605 	if (test->field_id == E_CONTACT_UID ||
2606 	    test->field_id == E_CONTACT_REV) {
2607 		/* UID & REV fields are not normalized in the summary */
2608 		e_cache_sqlite_stmt_append_printf (string, " = %Q", test->value);
2609 	} else {
2610 		normal = e_util_utf8_normalize (test->value);
2611 		e_cache_sqlite_stmt_append_printf (string, " = %Q", normal);
2612 		g_free (normal);
2613 	}
2614 }
2615 
2616 static void
field_test_query_contains(EBookCache * book_cache,GString * string,QueryFieldTest * test)2617 field_test_query_contains (EBookCache *book_cache,
2618 			   GString *string,
2619 			   QueryFieldTest *test)
2620 {
2621 	SummaryField *field = test->field;
2622 	gboolean need_escape;
2623 	gchar *escaped;
2624 
2625 	escaped = ebc_normalize_for_like (test, FALSE, &need_escape);
2626 
2627 	g_string_append_c (string, '(');
2628 
2629 	ebc_string_append_column (string, field, NULL);
2630 	g_string_append (string, " IS NOT NULL AND ");
2631 	ebc_string_append_column (string, field, NULL);
2632 	g_string_append (string, " LIKE '%");
2633 	g_string_append (string, escaped);
2634 	g_string_append (string, "%'");
2635 
2636 	if (need_escape)
2637 		g_string_append (string, EBC_ESCAPE_SEQUENCE);
2638 
2639 	g_string_append_c (string, ')');
2640 
2641 	g_free (escaped);
2642 }
2643 
2644 static void
field_test_query_begins_with(EBookCache * book_cache,GString * string,QueryFieldTest * test)2645 field_test_query_begins_with (EBookCache *book_cache,
2646 			      GString *string,
2647 			      QueryFieldTest *test)
2648 {
2649 	SummaryField *field = test->field;
2650 	gboolean need_escape;
2651 	gchar *escaped;
2652 
2653 	escaped = ebc_normalize_for_like (test, FALSE, &need_escape);
2654 
2655 	g_string_append_c (string, '(');
2656 	ebc_string_append_column (string, field, NULL);
2657 	g_string_append (string, " IS NOT NULL AND ");
2658 
2659 	ebc_string_append_column (string, field, NULL);
2660 	g_string_append (string, " LIKE \'");
2661 	g_string_append (string, escaped);
2662 	g_string_append (string, "%\'");
2663 
2664 	if (need_escape)
2665 		g_string_append (string, EBC_ESCAPE_SEQUENCE);
2666 	g_string_append_c (string, ')');
2667 
2668 	g_free (escaped);
2669 }
2670 
2671 static void
field_test_query_ends_with(EBookCache * book_cache,GString * string,QueryFieldTest * test)2672 field_test_query_ends_with (EBookCache *book_cache,
2673 			    GString *string,
2674 			    QueryFieldTest *test)
2675 {
2676 	SummaryField *field = test->field;
2677 	gboolean need_escape;
2678 	gchar *escaped;
2679 
2680 	if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
2681 		escaped = ebc_normalize_for_like (test, TRUE, &need_escape);
2682 
2683 		g_string_append_c (string, '(');
2684 		ebc_string_append_column (string, field, EBC_SUFFIX_REVERSE);
2685 		g_string_append (string, " IS NOT NULL AND ");
2686 
2687 		ebc_string_append_column (string, field, EBC_SUFFIX_REVERSE);
2688 		g_string_append (string, " LIKE \'");
2689 		g_string_append (string, escaped);
2690 		g_string_append (string, "%\'");
2691 	} else {
2692 		escaped = ebc_normalize_for_like (test, FALSE, &need_escape);
2693 		g_string_append_c (string, '(');
2694 
2695 		ebc_string_append_column (string, field, NULL);
2696 		g_string_append (string, " IS NOT NULL AND ");
2697 
2698 		ebc_string_append_column (string, field, NULL);
2699 		g_string_append (string, " LIKE \'%");
2700 		g_string_append (string, escaped);
2701 		g_string_append_c (string, '\'');
2702 	}
2703 
2704 	if (need_escape)
2705 		g_string_append (string, EBC_ESCAPE_SEQUENCE);
2706 
2707 	g_string_append_c (string, ')');
2708 	g_free (escaped);
2709 }
2710 
2711 static void
field_test_query_eqphone(EBookCache * book_cache,GString * string,QueryFieldTest * test)2712 field_test_query_eqphone (EBookCache *book_cache,
2713 			  GString *string,
2714 			  QueryFieldTest *test)
2715 {
2716 	SummaryField *field = test->field;
2717 	QueryPhoneTest *phone_test = (QueryPhoneTest *) test;
2718 
2719 	if ((field->index & INDEX_FLAG (PHONE)) != 0) {
2720 		g_string_append_c (string, '(');
2721 		ebc_string_append_column (string, field, EBC_SUFFIX_PHONE);
2722 		e_cache_sqlite_stmt_append_printf (string, " = %Q AND ", phone_test->national);
2723 
2724 		/* For exact matches, a country code qualifier is required by both
2725 		 * query input and row input
2726 		 */
2727 		ebc_string_append_column (string, field, EBC_SUFFIX_COUNTRY);
2728 		g_string_append (string, " != 0 AND ");
2729 
2730 		ebc_string_append_column (string, field, EBC_SUFFIX_COUNTRY);
2731 		e_cache_sqlite_stmt_append_printf (string, " = %d", phone_test->country);
2732 		g_string_append_c (string, ')');
2733 	} else {
2734 		/* No indexed columns available, perform the fallback */
2735 		g_string_append (string, EBC_FUNC_EQPHONE_EXACT " (");
2736 		ebc_string_append_column (string, field, NULL);
2737 		e_cache_sqlite_stmt_append_printf (string, ", %Q)", test->value);
2738 	}
2739 }
2740 
2741 static void
field_test_query_eqphone_national(EBookCache * book_cache,GString * string,QueryFieldTest * test)2742 field_test_query_eqphone_national (EBookCache *book_cache,
2743 				   GString *string,
2744 				   QueryFieldTest *test)
2745 {
2746 
2747 	SummaryField *field = test->field;
2748 	QueryPhoneTest *phone_test = (QueryPhoneTest *) test;
2749 
2750 	if ((field->index & INDEX_FLAG (PHONE)) != 0) {
2751 		/* Only a compound expression if there is a country code */
2752 		if (phone_test->country)
2753 			g_string_append_c (string, '(');
2754 
2755 		/* Generate: phone = %Q */
2756 		ebc_string_append_column (string, field, EBC_SUFFIX_PHONE);
2757 		e_cache_sqlite_stmt_append_printf (string, " = %Q", phone_test->national);
2758 
2759 		/* When doing a national search, no need to check country
2760 		 * code unless the query number also has a country code
2761 		 */
2762 		if (phone_test->country) {
2763 			/* Generate: (phone = %Q AND (country = 0 OR country = %d)) */
2764 			g_string_append (string, " AND (");
2765 			ebc_string_append_column (string, field, EBC_SUFFIX_COUNTRY);
2766 			g_string_append (string, " = 0 OR ");
2767 			ebc_string_append_column (string, field, EBC_SUFFIX_COUNTRY);
2768 			e_cache_sqlite_stmt_append_printf (string, " = %d))", phone_test->country);
2769 		}
2770 	} else {
2771 		/* No indexed columns available, perform the fallback */
2772 		g_string_append (string, EBC_FUNC_EQPHONE_NATIONAL " (");
2773 		ebc_string_append_column (string, field, NULL);
2774 		e_cache_sqlite_stmt_append_printf (string, ", %Q)", test->value);
2775 	}
2776 }
2777 
2778 static void
field_test_query_eqphone_short(EBookCache * book_cache,GString * string,QueryFieldTest * test)2779 field_test_query_eqphone_short (EBookCache *book_cache,
2780 				GString *string,
2781 				QueryFieldTest *test)
2782 {
2783 	SummaryField *field = test->field;
2784 
2785 	/* No quick way to do the short match */
2786 	g_string_append (string, EBC_FUNC_EQPHONE_SHORT " (");
2787 	ebc_string_append_column (string, field, NULL);
2788 	e_cache_sqlite_stmt_append_printf (string, ", %Q)", test->value);
2789 }
2790 
2791 static void
field_test_query_regex_normal(EBookCache * book_cache,GString * string,QueryFieldTest * test)2792 field_test_query_regex_normal (EBookCache *book_cache,
2793 			       GString *string,
2794 			       QueryFieldTest *test)
2795 {
2796 	SummaryField *field = test->field;
2797 	gchar *normal;
2798 
2799 	normal = e_util_utf8_normalize (test->value);
2800 
2801 	if (field->aux_table) {
2802 		e_cache_sqlite_stmt_append_printf (
2803 			string, "%s.value REGEXP %Q",
2804 			field->aux_table_symbolic,
2805 			normal);
2806 	} else {
2807 		e_cache_sqlite_stmt_append_printf (
2808 			string, "summary.%s REGEXP %Q",
2809 			field->dbname,
2810 			normal);
2811 	}
2812 
2813 	g_free (normal);
2814 }
2815 
2816 static void
field_test_query_exists(EBookCache * book_cache,GString * string,QueryFieldTest * test)2817 field_test_query_exists (EBookCache *book_cache,
2818 			 GString *string,
2819 			 QueryFieldTest *test)
2820 {
2821 	SummaryField *field = test->field;
2822 
2823 	ebc_string_append_column (string, field, NULL);
2824 
2825 	if (test->field->type == E_TYPE_CONTACT_CERT)
2826 		e_cache_sqlite_stmt_append_printf (string, " IS NOT '0'");
2827 	else
2828 		e_cache_sqlite_stmt_append_printf (string, " IS NOT NULL");
2829 }
2830 
2831 /* Lookup table for field test generators per EBookQueryTest,
2832  *
2833  * WARNING: This must stay in line with the EBookQueryTest definition.
2834  */
2835 static const GenerateFieldTest field_test_func_table[] = {
2836 	field_test_query_is,               /* E_BOOK_QUERY_IS */
2837 	field_test_query_contains,         /* E_BOOK_QUERY_CONTAINS */
2838 	field_test_query_begins_with,      /* E_BOOK_QUERY_BEGINS_WITH */
2839 	field_test_query_ends_with,        /* E_BOOK_QUERY_ENDS_WITH */
2840 	field_test_query_eqphone,          /* E_BOOK_QUERY_EQUALS_PHONE_NUMBER */
2841 	field_test_query_eqphone_national, /* E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER */
2842 	field_test_query_eqphone_short,    /* E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER */
2843 	field_test_query_regex_normal,     /* E_BOOK_QUERY_REGEX_NORMAL */
2844 	NULL /* Requires fallback */,      /* E_BOOK_QUERY_REGEX_RAW  */
2845 	field_test_query_exists,           /* BOOK_QUERY_EXISTS */
2846 	NULL /* Requires fallback */       /* BOOK_QUERY_EXISTS_VCARD */
2847 };
2848 
2849 /**********************************************************
2850  *                   Querying Contacts                    *
2851  **********************************************************/
2852 
2853 /* The various search types indicate what should be fetched
2854  */
2855 typedef enum {
2856 	SEARCH_FULL,          /* Get a list of EBookCacheSearchData*/
2857 	SEARCH_UID_AND_REV,   /* Get a list of EBookCacheSearchData, with shallow vcards only containing UID & REV */
2858 	SEARCH_UID,           /* Get a list of UID strings */
2859 	SEARCH_COUNT,         /* Get the number of matching rows */
2860 } SearchType;
2861 
2862 static void
ebc_generate_constraints(EBookCache * book_cache,GString * string,GPtrArray * constraints,const gchar * sexp)2863 ebc_generate_constraints (EBookCache *book_cache,
2864 			  GString *string,
2865 			  GPtrArray *constraints,
2866 			  const gchar *sexp)
2867 {
2868 	SubQueryContext *ctx;
2869 	QueryDelimiter *delim;
2870 	QueryFieldTest *test;
2871 	QueryElement **elements;
2872 	gint n_elements, ii;
2873 
2874 	/* If there are no constraints, we generate the fallback constraint for 'sexp' */
2875 	if (constraints == NULL) {
2876 		e_cache_sqlite_stmt_append_printf (
2877 			string,
2878 			EBC_FUNC_COMPARE_VCARD " (%Q,summary." E_CACHE_COLUMN_OBJECT ")",
2879 			sexp);
2880 		return;
2881 	}
2882 
2883 	elements = (QueryElement **) constraints->pdata;
2884 	n_elements = constraints->len;
2885 
2886 	ctx = sub_query_context_new ();
2887 
2888 	for (ii = 0; ii < n_elements; ii++) {
2889 		GenerateFieldTest generate_test_func = NULL;
2890 
2891 		/* Seperate field tests with the appropriate grouping */
2892 		if (elements[ii]->query != BOOK_QUERY_SUB_END &&
2893 		    sub_query_context_increment (ctx) > 0) {
2894 			guint delim_type = sub_query_context_peek_type (ctx);
2895 
2896 			switch (delim_type) {
2897 			case BOOK_QUERY_SUB_AND:
2898 				g_string_append (string, " AND ");
2899 				break;
2900 
2901 			case BOOK_QUERY_SUB_OR:
2902 				g_string_append (string, " OR ");
2903 				break;
2904 
2905 			case BOOK_QUERY_SUB_NOT:
2906 				/* Nothing to do between children of NOT,
2907 				 * there should only ever be one child of NOT anyway
2908 				 */
2909 				break;
2910 
2911 			case BOOK_QUERY_SUB_END:
2912 			default:
2913 				g_warn_if_reached ();
2914 			}
2915 		}
2916 
2917 		if (elements[ii]->query >= BOOK_QUERY_SUB_FIRST) {
2918 			delim = (QueryDelimiter *) elements[ii];
2919 
2920 			switch (delim->query) {
2921 			case BOOK_QUERY_SUB_NOT:
2922 				/* NOT is a unary operator and as such
2923 				 * comes before the opening parenthesis
2924 				 */
2925 				g_string_append (string, "NOT ");
2926 
2927 				/* Fall through */
2928 
2929 			case BOOK_QUERY_SUB_AND:
2930 			case BOOK_QUERY_SUB_OR:
2931 				/* Open a grouped statement and push the context */
2932 				sub_query_context_push (ctx, delim->query, FALSE);
2933 				g_string_append_c (string, '(');
2934 				break;
2935 
2936 			case BOOK_QUERY_SUB_END:
2937 				/* Close a grouped statement and pop the context */
2938 				g_string_append_c (string, ')');
2939 				sub_query_context_pop (ctx);
2940 				break;
2941 			default:
2942 				g_warn_if_reached ();
2943 			}
2944 
2945 			continue;
2946 		}
2947 
2948 		/* Find the appropriate field test generator */
2949 		test = (QueryFieldTest *) elements[ii];
2950 		if (test->query < G_N_ELEMENTS (field_test_func_table))
2951 			generate_test_func = field_test_func_table[test->query];
2952 
2953 		/* These should never happen, if it does it should be
2954 		 * fixed in the preflight checks
2955 		 */
2956 		g_warn_if_fail (generate_test_func != NULL);
2957 		g_warn_if_fail (test->field != NULL);
2958 
2959 		/* Generate the field test */
2960 		/* coverity[var_deref_op] */
2961 		generate_test_func (book_cache, string, test);
2962 	}
2963 
2964 	sub_query_context_free (ctx);
2965 }
2966 
2967 static void
ebc_search_meta_contacts_cb(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,const gchar * extra,gpointer out_value)2968 ebc_search_meta_contacts_cb (ECache *cache,
2969 			     const gchar *uid,
2970 			     const gchar *revision,
2971 			     const gchar *object,
2972 			     const gchar *extra,
2973 			     gpointer out_value)
2974 {
2975 	GSList **out_list = out_value;
2976 	EBookCacheSearchData *sd;
2977 	EContact *contact;
2978 	gchar *vcard;
2979 
2980 	g_return_if_fail (out_list != NULL);
2981 
2982 	contact = e_contact_new ();
2983 
2984 	e_contact_set (contact, E_CONTACT_UID, uid);
2985 	if (revision)
2986 		e_contact_set (contact, E_CONTACT_REV, revision);
2987 
2988 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
2989 
2990 	g_object_unref (contact);
2991 
2992 	sd = e_book_cache_search_data_new (uid, vcard, extra);
2993 
2994 	*out_list = g_slist_prepend (*out_list, sd);
2995 
2996 	g_free (vcard);
2997 }
2998 
2999 static void
ebc_search_full_contacts_cb(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,const gchar * extra,gpointer out_value)3000 ebc_search_full_contacts_cb (ECache *cache,
3001 			     const gchar *uid,
3002 			     const gchar *revision,
3003 			     const gchar *object,
3004 			     const gchar *extra,
3005 			     gpointer out_value)
3006 {
3007 	GSList **out_list = out_value;
3008 	EBookCacheSearchData *sd;
3009 
3010 	g_return_if_fail (out_list != NULL);
3011 
3012 	sd = e_book_cache_search_data_new (uid, object, extra);
3013 
3014 	*out_list = g_slist_prepend (*out_list, sd);
3015 }
3016 
3017 static void
ebc_search_uids_cb(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,const gchar * extra,gpointer out_value)3018 ebc_search_uids_cb (ECache *cache,
3019 		    const gchar *uid,
3020 		    const gchar *revision,
3021 		    const gchar *object,
3022 		    const gchar *extra,
3023 		    gpointer out_value)
3024 {
3025 	GSList **out_list = out_value;
3026 
3027 	g_return_if_fail (out_list != NULL);
3028 
3029 	*out_list = g_slist_prepend (*out_list, g_strdup (uid));
3030 }
3031 
3032 typedef void (* EBookCacheInternalSearchFunc)	(ECache *cache,
3033 						 const gchar *uid,
3034 						 const gchar *revision,
3035 						 const gchar *object,
3036 						 const gchar *extra,
3037 						 gpointer out_value);
3038 
3039 /* Generates the SELECT portion of the query, this will take care of
3040  * preparing the context of the query, and add the needed JOIN statements
3041  * based on which fields are referenced in the query expression.
3042  *
3043  * This also handles getting the correct callback and asking for the
3044  * right data depending on the 'search_type'
3045  */
3046 static EBookCacheInternalSearchFunc
ebc_generate_select(EBookCache * book_cache,GString * string,SearchType search_type,PreflightContext * context,GError ** error)3047 ebc_generate_select (EBookCache *book_cache,
3048 		     GString *string,
3049 		     SearchType search_type,
3050 		     PreflightContext *context,
3051 		     GError **error)
3052 {
3053 	EBookCacheInternalSearchFunc callback = NULL;
3054 	gboolean add_auxiliary_tables = FALSE;
3055 	gint ii;
3056 
3057 	if (context->status == PREFLIGHT_OK &&
3058 	    context->aux_mask != 0)
3059 		add_auxiliary_tables = TRUE;
3060 
3061 	g_string_append (string, "SELECT ");
3062 	if (add_auxiliary_tables)
3063 		g_string_append (string, "DISTINCT ");
3064 
3065 	switch (search_type) {
3066 	case SEARCH_FULL:
3067 		callback = ebc_search_full_contacts_cb;
3068 		g_string_append (string, "summary." E_CACHE_COLUMN_UID ",");
3069 		g_string_append (string, "summary." E_CACHE_COLUMN_REVISION ",");
3070 		g_string_append (string, "summary." E_CACHE_COLUMN_OBJECT ",");
3071 		g_string_append (string, "summary." E_CACHE_COLUMN_STATE ",");
3072 		g_string_append (string, "summary." EBC_COLUMN_EXTRA ",");
3073 		g_string_append (string, "summary." EBC_COLUMN_CUSTOM_FLAGS " ");
3074 		break;
3075 	case SEARCH_UID_AND_REV:
3076 		callback = ebc_search_meta_contacts_cb;
3077 		g_string_append (string, "summary." E_CACHE_COLUMN_UID ",");
3078 		g_string_append (string, "summary." E_CACHE_COLUMN_REVISION ",");
3079 		g_string_append (string, "summary." EBC_COLUMN_EXTRA ",");
3080 		g_string_append (string, "summary." EBC_COLUMN_CUSTOM_FLAGS " ");
3081 		break;
3082 	case SEARCH_UID:
3083 		callback = ebc_search_uids_cb;
3084 		g_string_append (string, "summary." E_CACHE_COLUMN_UID ",");
3085 		g_string_append (string, "summary." E_CACHE_COLUMN_REVISION " ");
3086 		break;
3087 	case SEARCH_COUNT:
3088 		if (context->aux_mask != 0)
3089 			g_string_append (string, "count (DISTINCT summary." E_CACHE_COLUMN_UID ") ");
3090 		else
3091 			g_string_append (string, "count (*) ");
3092 		break;
3093 	}
3094 
3095 	e_cache_sqlite_stmt_append_printf (string, "FROM %Q AS summary", E_CACHE_TABLE_OBJECTS);
3096 
3097 	/* Add any required auxiliary tables into the query context */
3098 	if (add_auxiliary_tables) {
3099 		for (ii = 0; ii < book_cache->priv->n_summary_fields; ii++) {
3100 
3101 			/* We cap this at EBC_MAX_SUMMARY_FIELDS (64 bits) at creation time */
3102 			if ((context->aux_mask & (1 << ii)) != 0) {
3103 				SummaryField *field = &(book_cache->priv->summary_fields[ii]);
3104 				gboolean left_join = (context->left_join_mask >> ii) & 1;
3105 
3106 				/* Note the '+' in the JOIN statement.
3107 				 *
3108 				 * This plus makes the uid's index ineligable to participate
3109 				 * in any indexing.
3110 				 *
3111 				 * Without this, the indexes which we prefer for prefix or
3112 				 * suffix matching in the auxiliary tables are ignored and
3113 				 * only considered on exact matches.
3114 				 *
3115 				 * This is crucial to ensure that the uid index does not
3116 				 * compete with the value index in constraints such as:
3117 				 *
3118 				 *     WHERE email_list.value LIKE "boogieman%"
3119 				 */
3120 				e_cache_sqlite_stmt_append_printf (
3121 					string, " %sJOIN %Q AS %s ON %s%s.uid = summary." E_CACHE_COLUMN_UID,
3122 					left_join ? "LEFT " : "",
3123 					field->aux_table,
3124 					field->aux_table_symbolic,
3125 					left_join ? "" : "+",
3126 					field->aux_table_symbolic);
3127 			}
3128 		}
3129 	}
3130 
3131 	return callback;
3132 }
3133 
3134 static gboolean
ebc_is_autocomplete_query(PreflightContext * context)3135 ebc_is_autocomplete_query (PreflightContext *context)
3136 {
3137 	QueryFieldTest *test;
3138 	QueryElement **elements;
3139 	gint n_elements, ii;
3140 	int non_aux_fields = 0;
3141 
3142 	if (context->status != PREFLIGHT_OK || context->aux_mask == 0)
3143 		return FALSE;
3144 
3145 	elements = (QueryElement **) context->constraints->pdata;
3146 	n_elements = context->constraints->len;
3147 
3148 	for (ii = 0; ii < n_elements; ii++) {
3149 		test = (QueryFieldTest *) elements[ii];
3150 
3151 		/* For these, check if the field being operated on is
3152 		   an auxiliary field or not. */
3153 		if (elements[ii]->query == E_BOOK_QUERY_BEGINS_WITH ||
3154 		    elements[ii]->query == E_BOOK_QUERY_ENDS_WITH ||
3155 		    elements[ii]->query == E_BOOK_QUERY_IS ||
3156 		    elements[ii]->query == BOOK_QUERY_EXISTS ||
3157 		    elements[ii]->query == E_BOOK_QUERY_CONTAINS) {
3158 			if (test->field->type != E_TYPE_CONTACT_ATTR_LIST)
3159 				non_aux_fields++;
3160 			continue;
3161 		}
3162 
3163 		/* Nothing else is allowed other than "(or" ... ")" */
3164 		if (elements[ii]->query != BOOK_QUERY_SUB_OR &&
3165 		    elements[ii]->query != BOOK_QUERY_SUB_END)
3166 			return FALSE;
3167 	}
3168 
3169 	/* If there were no non-aux fields being queried, don't bother */
3170 	return non_aux_fields != 0;
3171 }
3172 
3173 static EBookCacheInternalSearchFunc
ebc_generate_autocomplete_query(EBookCache * book_cache,GString * string,SearchType search_type,PreflightContext * context,GError ** error)3174 ebc_generate_autocomplete_query (EBookCache *book_cache,
3175 				 GString *string,
3176 				 SearchType search_type,
3177 				 PreflightContext *context,
3178 				 GError **error)
3179 {
3180 	QueryElement **elements;
3181 	gint n_elements, ii;
3182 	guint64 aux_mask = context->aux_mask;
3183 	guint64 left_join_mask = context->left_join_mask;
3184 	EBookCacheInternalSearchFunc callback;
3185 	gboolean first = TRUE;
3186 
3187 	elements = (QueryElement **) context->constraints->pdata;
3188 	n_elements = context->constraints->len;
3189 
3190 	/* First the queries which use aux tables. */
3191 	for (ii = 0; ii < n_elements; ii++) {
3192 		GenerateFieldTest generate_test_func = NULL;
3193 		QueryFieldTest *test;
3194 		gint aux_index;
3195 
3196 		if (elements[ii]->query == BOOK_QUERY_SUB_OR ||
3197 		    elements[ii]->query == BOOK_QUERY_SUB_END)
3198 			continue;
3199 
3200 		test = (QueryFieldTest *) elements[ii];
3201 		if (test->field->type != E_TYPE_CONTACT_ATTR_LIST)
3202 			continue;
3203 
3204 		aux_index = summary_field_get_index (book_cache, test->field_id);
3205 		g_warn_if_fail (aux_index >= 0 && aux_index < EBC_MAX_SUMMARY_FIELDS);
3206 
3207 		/* Just to mute a compiler warning when aux_index == -1 */
3208 		aux_index = ABS (aux_index);
3209 		context->aux_mask = (1 << aux_index);
3210 		context->left_join_mask = 0;
3211 
3212 		callback = ebc_generate_select (book_cache, string, search_type, context, error);
3213 		e_cache_sqlite_stmt_append_printf (string, " WHERE summary." E_CACHE_COLUMN_STATE "!=%d AND (", E_OFFLINE_STATE_LOCALLY_DELETED);
3214 		context->aux_mask = aux_mask;
3215 		context->left_join_mask = left_join_mask;
3216 		if (!callback)
3217 			return NULL;
3218 
3219 		generate_test_func = field_test_func_table[test->query];
3220 		generate_test_func (book_cache, string, test);
3221 
3222 		g_string_append (string, ") UNION ");
3223 	}
3224 
3225 	/* Finally, generate the SELECT for the primary fields. */
3226 	context->aux_mask = 0;
3227 	callback = ebc_generate_select (book_cache, string, search_type, context, error);
3228 	context->aux_mask = aux_mask;
3229 	if (!callback)
3230 		return NULL;
3231 
3232 	e_cache_sqlite_stmt_append_printf (string, " WHERE summary." E_CACHE_COLUMN_STATE "!=%d AND (", E_OFFLINE_STATE_LOCALLY_DELETED);
3233 
3234 	for (ii = 0; ii < n_elements; ii++) {
3235 		GenerateFieldTest generate_test_func = NULL;
3236 		QueryFieldTest *test;
3237 
3238 		if (elements[ii]->query == BOOK_QUERY_SUB_OR ||
3239 		    elements[ii]->query == BOOK_QUERY_SUB_END)
3240 			continue;
3241 
3242 		test = (QueryFieldTest *) elements[ii];
3243 		if (test->field->type == E_TYPE_CONTACT_ATTR_LIST)
3244 			continue;
3245 
3246 		if (!first)
3247 			g_string_append (string, " OR ");
3248 		else
3249 			first = FALSE;
3250 
3251 		generate_test_func = field_test_func_table[test->query];
3252 		generate_test_func (book_cache, string, test);
3253 	}
3254 
3255 	g_string_append_c (string, ')');
3256 
3257 	return callback;
3258 }
3259 
3260 struct EBCSearchData {
3261 	gint uid_index;
3262 	gint revision_index;
3263 	gint object_index;
3264 	gint extra_index;
3265 	gint custom_flags_index;
3266 	gint state_index;
3267 
3268 	EBookCacheInternalSearchFunc func;
3269 	gpointer out_value;
3270 
3271 	EBookCacheSearchFunc user_func;
3272 	gpointer user_func_user_data;
3273 };
3274 
3275 static gboolean
ebc_search_select_cb(ECache * cache,gint ncols,const gchar * column_names[],const gchar * column_values[],gpointer user_data)3276 ebc_search_select_cb (ECache *cache,
3277 		      gint ncols,
3278 		      const gchar *column_names[],
3279 		      const gchar *column_values[],
3280 		      gpointer user_data)
3281 {
3282 	struct EBCSearchData *sd = user_data;
3283 	const gchar *object = NULL, *extra = NULL;
3284 	guint32 custom_flags = 0;
3285 	EOfflineState offline_state = E_OFFLINE_STATE_UNKNOWN;
3286 
3287 	g_return_val_if_fail (sd != NULL, FALSE);
3288 	g_return_val_if_fail (sd->func != NULL || sd->user_func != NULL, FALSE);
3289 	g_return_val_if_fail (sd->out_value != NULL || sd->user_func != NULL, FALSE);
3290 
3291 	if (sd->uid_index == -1 ||
3292 	    sd->revision_index == -1 ||
3293 	    sd->object_index == -1 ||
3294 	    sd->extra_index == -1 ||
3295 	    sd->custom_flags_index == -1 ||
3296 	    sd->state_index == -1) {
3297 		gint ii;
3298 
3299 		for (ii = 0; ii < ncols && (sd->uid_index == -1 ||
3300 		     sd->revision_index == -1 ||
3301 		     sd->object_index == -1 ||
3302 		     sd->extra_index == -1 ||
3303 		     sd->custom_flags_index == -1 ||
3304 		     sd->state_index == -1); ii++) {
3305 			const gchar *cname = column_names[ii];
3306 
3307 			if (!cname)
3308 				continue;
3309 
3310 			if (g_str_has_prefix (cname, "summary."))
3311 				cname += 8;
3312 
3313 			if (sd->uid_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_UID) == 0) {
3314 				sd->uid_index = ii;
3315 			} else if (sd->revision_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_REVISION) == 0) {
3316 				sd->revision_index = ii;
3317 			} else if (sd->object_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_OBJECT) == 0) {
3318 				sd->object_index = ii;
3319 			} else if (sd->extra_index == -1 && g_ascii_strcasecmp (cname, EBC_COLUMN_EXTRA) == 0) {
3320 				sd->extra_index = ii;
3321 			} else if (sd->custom_flags_index == -1 && g_ascii_strcasecmp (cname, EBC_COLUMN_CUSTOM_FLAGS) == 0) {
3322 				sd->custom_flags_index = ii;
3323 			} else if (sd->state_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_STATE) == 0) {
3324 				sd->state_index = ii;
3325 			}
3326 		}
3327 	}
3328 
3329 	g_return_val_if_fail (sd->uid_index >= 0 && sd->uid_index < ncols, FALSE);
3330 	g_return_val_if_fail (sd->revision_index >= 0 && sd->revision_index < ncols, FALSE);
3331 
3332 	if (sd->object_index != -2) {
3333 		g_return_val_if_fail (sd->object_index >= 0 && sd->object_index < ncols, FALSE);
3334 		object = column_values[sd->object_index];
3335 	}
3336 
3337 	if (sd->extra_index != -2) {
3338 		g_return_val_if_fail (sd->extra_index >= 0 && sd->extra_index < ncols, FALSE);
3339 		extra = column_values[sd->extra_index];
3340 	}
3341 
3342 	if (sd->custom_flags_index != -2) {
3343 		g_return_val_if_fail (sd->custom_flags_index >= 0 && sd->custom_flags_index < ncols, FALSE);
3344 		if (column_values[sd->custom_flags_index])
3345 			custom_flags = g_ascii_strtoull (column_values[sd->custom_flags_index], NULL, 10);
3346 	}
3347 
3348 	if (sd->state_index != -2) {
3349 		g_return_val_if_fail (sd->extra_index >= 0 && sd->extra_index < ncols, FALSE);
3350 
3351 		if (!column_values[sd->state_index])
3352 			offline_state = E_OFFLINE_STATE_UNKNOWN;
3353 		else
3354 			offline_state = g_ascii_strtoull (column_values[sd->state_index], NULL, 10);
3355 	}
3356 
3357 	if (sd->user_func) {
3358 		return sd->user_func (E_BOOK_CACHE (cache), column_values[sd->uid_index], column_values[sd->revision_index],
3359 			object, extra, custom_flags, offline_state, sd->user_func_user_data);
3360 	}
3361 
3362 	sd->func (cache, column_values[sd->uid_index], column_values[sd->revision_index], object, extra, sd->out_value);
3363 
3364 	return TRUE;
3365 }
3366 
3367 static gboolean
ebc_do_search_query(EBookCache * book_cache,PreflightContext * context,const gchar * sexp,SearchType search_type,gpointer out_value,EBookCacheSearchFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)3368 ebc_do_search_query (EBookCache *book_cache,
3369 		     PreflightContext *context,
3370 		     const gchar *sexp,
3371 		     SearchType search_type,
3372 		     gpointer out_value,
3373 		     EBookCacheSearchFunc func,
3374 		     gpointer func_user_data,
3375 		     GCancellable *cancellable,
3376 		     GError **error)
3377 {
3378 	struct EBCSearchData sd;
3379 	GString *stmt;
3380 	gboolean success = FALSE;
3381 
3382 	/* We might calculate a reasonable estimation of bytes
3383 	 * during the preflight checks */
3384 	stmt = g_string_sized_new (GENERATED_QUERY_BYTES);
3385 
3386 	/* Extra special case. For the common case of the email composer's
3387 	   addressbook autocompletion, we really want the most optimal query.
3388 	   So check for it and use a basically hand-crafted one. */
3389         if (ebc_is_autocomplete_query (context)) {
3390 		sd.func = ebc_generate_autocomplete_query (book_cache, stmt, search_type, context, error);
3391 	} else {
3392 		/* Generate the leading SELECT statement */
3393 		sd.func = ebc_generate_select (book_cache, stmt, search_type, context, error);
3394 
3395 		if (sd.func) {
3396 			e_cache_sqlite_stmt_append_printf (stmt,
3397 				" WHERE summary." E_CACHE_COLUMN_STATE "!=%d",
3398 				E_OFFLINE_STATE_LOCALLY_DELETED);
3399 
3400 			if (EBC_STATUS_GEN_CONSTRAINTS (context->status)) {
3401 				GString *where_clause = g_string_new ("");
3402 
3403 				/*
3404 				 * Now generate the search expression on the main contacts table
3405 				 */
3406 				ebc_generate_constraints (book_cache, where_clause, context->constraints, sexp);
3407 				if (where_clause->len)
3408 					e_cache_sqlite_stmt_append_printf (stmt, " AND (%s)", where_clause->str);
3409 				g_string_free (where_clause, TRUE);
3410 			}
3411 		}
3412 	}
3413 
3414 	if (sd.func) {
3415 		sd.uid_index = -1;
3416 		sd.revision_index = -1;
3417 		sd.object_index = search_type == SEARCH_FULL ? -1 : -2;
3418 		sd.extra_index = search_type == SEARCH_UID ? -2 : -1;
3419 		sd.custom_flags_index = search_type == SEARCH_UID ? -2 : -1;
3420 		sd.state_index = search_type == SEARCH_FULL ? -1 : -2;
3421 		sd.out_value = out_value;
3422 		sd.user_func = func;
3423 		sd.user_func_user_data = func_user_data;
3424 
3425 		success = e_cache_sqlite_select (E_CACHE (book_cache), stmt->str,
3426 			ebc_search_select_cb, &sd, cancellable, error);
3427 	}
3428 
3429 	g_string_free (stmt, TRUE);
3430 
3431 	return success;
3432 }
3433 
3434 static gboolean
ebc_search_internal(EBookCache * book_cache,const gchar * sexp,SearchType search_type,gpointer out_value,EBookCacheSearchFunc func,gpointer func_user_data,GCancellable * cancellable,GError ** error)3435 ebc_search_internal (EBookCache *book_cache,
3436 		     const gchar *sexp,
3437 		     SearchType search_type,
3438 		     gpointer out_value,
3439 		     EBookCacheSearchFunc func,
3440 		     gpointer func_user_data,
3441 		     GCancellable *cancellable,
3442 		     GError **error)
3443 {
3444 	PreflightContext context = PREFLIGHT_CONTEXT_INIT;
3445 	gboolean success = FALSE;
3446 
3447 	/* Now start with the query preflighting */
3448 	query_preflight (&context, book_cache, sexp);
3449 
3450 	switch (context.status) {
3451 	case PREFLIGHT_OK:
3452 	case PREFLIGHT_LIST_ALL:
3453 	case PREFLIGHT_NOT_SUMMARIZED:
3454 		/* No errors, let's really search */
3455 		success = ebc_do_search_query (
3456 			book_cache, &context, sexp,
3457 			search_type, out_value, func, func_user_data,
3458 			cancellable, error);
3459 		break;
3460 
3461 	case PREFLIGHT_INVALID:
3462 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
3463 			_("Invalid query: %s"), sexp);
3464 		break;
3465 
3466 	case PREFLIGHT_UNSUPPORTED:
3467 		g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_UNSUPPORTED_QUERY,
3468 			_("Query contained unsupported elements"));
3469 		break;
3470 	}
3471 
3472 	preflight_context_clear (&context);
3473 
3474 	return success;
3475 }
3476 
3477 /******************************************************************
3478  *                    EBookCacheCursor Implementation             *
3479  ******************************************************************/
3480 
3481 static EBookCacheCursor *
e_book_cache_cursor_fake_ref(EBookCacheCursor * cursor)3482 e_book_cache_cursor_fake_ref (EBookCacheCursor *cursor)
3483 {
3484 	/* Only for the introspection and the boxed type */
3485 	return cursor;
3486 }
3487 
3488 static void
e_book_cache_cursor_fake_unref(EBookCacheCursor * cursor)3489 e_book_cache_cursor_fake_unref (EBookCacheCursor *cursor)
3490 {
3491 	/* Only for the introspection and the boxed type;
3492 	   free with e_book_cache_cursor_free() instead */
3493 }
3494 
3495 typedef struct _CursorState CursorState;
3496 
3497 struct _CursorState {
3498 	gchar **values;			/* The current cursor position, results will be returned after this position */
3499 	gchar *last_uid; 		/* The current cursor contact UID position, used as a tie breaker */
3500 	EBookCacheCursorOrigin position;/* The position is updated with the cursor state and is used to distinguish
3501 					 * between the beginning and the ending of the cursor's contact list.
3502 					 * While the cursor is in a non-null state, the position will be
3503 					 * E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT.
3504 					 */
3505 };
3506 
3507 struct _EBookCacheCursor {
3508 	EBookBackendSExp *sexp;       /* An EBookBackendSExp based on the query, used by e_book_sqlite_cursor_compare () */
3509 	gchar         *select_vcards; /* The first fragment when querying results */
3510 	gchar         *select_count;  /* The first fragment when querying contact counts */
3511 	gchar         *query;         /* The SQL query expression derived from the passed search expression */
3512 	gchar         *order;         /* The normal order SQL query fragment to append at the end, containing ORDER BY etc */
3513 	gchar         *reverse_order; /* The reverse order SQL query fragment to append at the end, containing ORDER BY etc */
3514 
3515 	EContactField       *sort_fields;   /* The fields to sort in a query in the order or sort priority */
3516 	EBookCursorSortType *sort_types;    /* The sort method to use for each field */
3517 	gint                 n_sort_fields; /* The amound of sort fields */
3518 
3519 	CursorState          state;
3520 };
3521 
3522 static CursorState *cursor_state_copy             (EBookCacheCursor     *cursor,
3523 						   CursorState          *state);
3524 static void         cursor_state_free             (EBookCacheCursor     *cursor,
3525 						   CursorState          *state);
3526 static void         cursor_state_clear            (EBookCacheCursor     *cursor,
3527 						   CursorState          *state,
3528 						   EBookCacheCursorOrigin position);
3529 static void         cursor_state_set_from_contact (EBookCache           *book_cache,
3530 						   EBookCacheCursor     *cursor,
3531 						   CursorState          *state,
3532 						   EContact             *contact);
3533 static void         cursor_state_set_from_vcard   (EBookCache           *book_cache,
3534 						   EBookCacheCursor     *cursor,
3535 						   CursorState          *state,
3536 						   const gchar          *vcard);
3537 
3538 static CursorState *
cursor_state_copy(EBookCacheCursor * cursor,CursorState * state)3539 cursor_state_copy (EBookCacheCursor *cursor,
3540 		   CursorState *state)
3541 {
3542 	CursorState *copy;
3543 	gint ii;
3544 
3545 	copy = g_slice_new0 (CursorState);
3546 	copy->values = g_new0 (gchar *, cursor->n_sort_fields);
3547 
3548 	for (ii = 0; ii < cursor->n_sort_fields; ii++) {
3549 		copy->values[ii] = g_strdup (state->values[ii]);
3550 	}
3551 
3552 	copy->last_uid = g_strdup (state->last_uid);
3553 	copy->position = state->position;
3554 
3555 	return copy;
3556 }
3557 
3558 static void
cursor_state_free(EBookCacheCursor * cursor,CursorState * state)3559 cursor_state_free (EBookCacheCursor *cursor,
3560 		   CursorState *state)
3561 {
3562 	if (state) {
3563 		cursor_state_clear (cursor, state, E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN);
3564 		g_free (state->values);
3565 		g_slice_free (CursorState, state);
3566 	}
3567 }
3568 
3569 static void
cursor_state_clear(EBookCacheCursor * cursor,CursorState * state,EBookCacheCursorOrigin position)3570 cursor_state_clear (EBookCacheCursor *cursor,
3571 		    CursorState *state,
3572 		    EBookCacheCursorOrigin position)
3573 {
3574 	gint ii;
3575 
3576 	for (ii = 0; ii < cursor->n_sort_fields; ii++) {
3577 		g_free (state->values[ii]);
3578 		state->values[ii] = NULL;
3579 	}
3580 
3581 	g_free (state->last_uid);
3582 	state->last_uid = NULL;
3583 	state->position = position;
3584 }
3585 
3586 static void
cursor_state_set_from_contact(EBookCache * book_cache,EBookCacheCursor * cursor,CursorState * state,EContact * contact)3587 cursor_state_set_from_contact (EBookCache *book_cache,
3588 			       EBookCacheCursor *cursor,
3589 			       CursorState *state,
3590 			       EContact *contact)
3591 {
3592 	gint ii;
3593 
3594 	cursor_state_clear (cursor, state, E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN);
3595 
3596 	for (ii = 0; ii < cursor->n_sort_fields; ii++) {
3597 		const gchar *string = e_contact_get_const (contact, cursor->sort_fields[ii]);
3598 		SummaryField *field;
3599 		gchar *sort_key;
3600 
3601 		if (string)
3602 			sort_key = e_collator_generate_key (book_cache->priv->collator, string, NULL);
3603 		else
3604 			sort_key = g_strdup ("");
3605 
3606 		field = summary_field_get (book_cache, cursor->sort_fields[ii]);
3607 
3608 		if (field && (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
3609 			state->values[ii] = sort_key;
3610 		} else {
3611 			state->values[ii] = ebc_encode_vcard_sort_key (sort_key);
3612 			g_free (sort_key);
3613 		}
3614 	}
3615 
3616 	state->last_uid = e_contact_get (contact, E_CONTACT_UID);
3617 	state->position = E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT;
3618 }
3619 
3620 static void
cursor_state_set_from_vcard(EBookCache * book_cache,EBookCacheCursor * cursor,CursorState * state,const gchar * vcard)3621 cursor_state_set_from_vcard (EBookCache *book_cache,
3622 			     EBookCacheCursor *cursor,
3623 			     CursorState *state,
3624 			     const gchar *vcard)
3625 {
3626 	EContact *contact;
3627 
3628 	contact = e_contact_new_from_vcard (vcard);
3629 	cursor_state_set_from_contact (book_cache, cursor, state, contact);
3630 	g_object_unref (contact);
3631 }
3632 
3633 static gboolean
ebc_cursor_setup_query(EBookCache * book_cache,EBookCacheCursor * cursor,const gchar * sexp,GError ** error)3634 ebc_cursor_setup_query (EBookCache *book_cache,
3635 			EBookCacheCursor *cursor,
3636 			const gchar *sexp,
3637 			GError **error)
3638 {
3639 	PreflightContext context = PREFLIGHT_CONTEXT_INIT;
3640 	GString *string, *where_clause;
3641 
3642 	/* Preflighting and error checking */
3643 	if (sexp) {
3644 		query_preflight (&context, book_cache, sexp);
3645 
3646 		if (context.status > PREFLIGHT_NOT_SUMMARIZED) {
3647 			g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
3648 				_("Invalid query for a book cursor"));
3649 
3650 			preflight_context_clear (&context);
3651 			return FALSE;
3652 		}
3653 	}
3654 
3655 	/* Now we caught the errors, let's generate our queries and get out of here ... */
3656 	g_free (cursor->select_vcards);
3657 	g_free (cursor->select_count);
3658 	g_free (cursor->query);
3659 	g_clear_object (&(cursor->sexp));
3660 
3661 	/* Generate the leading SELECT portions that we need */
3662 	string = g_string_new ("");
3663 	ebc_generate_select (book_cache, string, SEARCH_FULL, &context, NULL);
3664 	cursor->select_vcards = g_string_free (string, FALSE);
3665 
3666 	string = g_string_new ("");
3667 	ebc_generate_select (book_cache, string, SEARCH_COUNT, &context, NULL);
3668 	cursor->select_count = g_string_free (string, FALSE);
3669 
3670 	where_clause = g_string_new ("");
3671 
3672 	e_cache_sqlite_stmt_append_printf (where_clause, "summary." E_CACHE_COLUMN_STATE "!=%d",
3673 		E_OFFLINE_STATE_LOCALLY_DELETED);
3674 
3675 	if (!sexp || context.status == PREFLIGHT_LIST_ALL) {
3676 		cursor->sexp = NULL;
3677 	} else {
3678 		cursor->sexp = e_book_backend_sexp_new (sexp);
3679 
3680 		string = g_string_new (NULL);
3681 		ebc_generate_constraints (book_cache, string, context.constraints, sexp);
3682 		if (string->len)
3683 			e_cache_sqlite_stmt_append_printf (where_clause, " AND (%s)", string->str);
3684 		g_string_free (string, TRUE);
3685 	}
3686 
3687 	cursor->query = g_string_free (where_clause, FALSE);
3688 
3689 	preflight_context_clear (&context);
3690 
3691 	return TRUE;
3692 }
3693 
3694 static gchar *
ebc_cursor_order_by_fragment(EBookCache * book_cache,const EContactField * sort_fields,const EBookCursorSortType * sort_types,guint n_sort_fields,gboolean reverse)3695 ebc_cursor_order_by_fragment (EBookCache *book_cache,
3696 			      const EContactField *sort_fields,
3697 			      const EBookCursorSortType *sort_types,
3698 			      guint n_sort_fields,
3699 			      gboolean reverse)
3700 {
3701 	GString *string;
3702 	gint ii;
3703 
3704 	string = g_string_new ("ORDER BY ");
3705 
3706 	for (ii = 0; ii < n_sort_fields; ii++) {
3707 		SummaryField *field = summary_field_get (book_cache, sort_fields[ii]);
3708 
3709 		if (ii > 0)
3710 			g_string_append (string, ", ");
3711 
3712 		if (field &&
3713 		    (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
3714 			g_string_append (string, "summary.");
3715 			g_string_append (string, field->dbname);
3716 			g_string_append (string, "_" EBC_SUFFIX_SORT_KEY " ");
3717 		} else {
3718 			g_string_append (string, "summary." E_CACHE_COLUMN_OBJECT);
3719 			g_string_append (string, " COLLATE ");
3720 			g_string_append (string, EBC_COLLATE_PREFIX);
3721 			g_string_append (string, e_contact_field_name (sort_fields[ii]));
3722 			g_string_append_c (string, ' ');
3723 		}
3724 
3725 		if (reverse)
3726 			g_string_append (string, (sort_types[ii] == E_BOOK_CURSOR_SORT_ASCENDING ? "DESC" : "ASC"));
3727 		else
3728 			g_string_append (string, (sort_types[ii] == E_BOOK_CURSOR_SORT_ASCENDING ? "ASC" : "DESC"));
3729 	}
3730 
3731 	/* Also order the UID, since it's our tie breaker */
3732 	if (n_sort_fields > 0)
3733 		g_string_append (string, ", ");
3734 
3735 	g_string_append (string, "summary." E_CACHE_COLUMN_UID " ");
3736 	g_string_append (string, reverse ? "DESC" : "ASC");
3737 
3738 	return g_string_free (string, FALSE);
3739 }
3740 
3741 static EBookCacheCursor *
ebc_cursor_new(EBookCache * book_cache,const gchar * sexp,const EContactField * sort_fields,const EBookCursorSortType * sort_types,guint n_sort_fields)3742 ebc_cursor_new (EBookCache *book_cache,
3743 		const gchar *sexp,
3744 		const EContactField *sort_fields,
3745 		const EBookCursorSortType *sort_types,
3746 		guint n_sort_fields)
3747 {
3748 	EBookCacheCursor *cursor = g_slice_new0 (EBookCacheCursor);
3749 
3750 	cursor->order = ebc_cursor_order_by_fragment (book_cache, sort_fields, sort_types, n_sort_fields, FALSE);
3751 	cursor->reverse_order = ebc_cursor_order_by_fragment (book_cache, sort_fields, sort_types, n_sort_fields, TRUE);
3752 
3753 	/* Sort parameters */
3754 	cursor->n_sort_fields = n_sort_fields;
3755 	cursor->sort_fields = g_memdup (sort_fields, sizeof (EContactField) * n_sort_fields);
3756 	cursor->sort_types = g_memdup (sort_types,  sizeof (EBookCursorSortType) * n_sort_fields);
3757 
3758 	/* Cursor state */
3759 	cursor->state.values = g_new0 (gchar *, n_sort_fields);
3760 	cursor->state.last_uid = NULL;
3761 	cursor->state.position = E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN;
3762 
3763 	return cursor;
3764 }
3765 
3766 static void
ebc_cursor_free(EBookCacheCursor * cursor)3767 ebc_cursor_free (EBookCacheCursor *cursor)
3768 {
3769 	if (cursor) {
3770 		cursor_state_clear (cursor, &(cursor->state), E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN);
3771 		g_free (cursor->state.values);
3772 
3773 		g_clear_object (&(cursor->sexp));
3774 		g_free (cursor->select_vcards);
3775 		g_free (cursor->select_count);
3776 		g_free (cursor->query);
3777 		g_free (cursor->order);
3778 		g_free (cursor->reverse_order);
3779 		g_free (cursor->sort_fields);
3780 		g_free (cursor->sort_types);
3781 
3782 		g_slice_free (EBookCacheCursor, cursor);
3783 	}
3784 }
3785 
3786 #define GREATER_OR_LESS(cursor, idx, reverse) \
3787 	(reverse ? \
3788 	 (((EBookCacheCursor *) cursor)->sort_types[idx] == E_BOOK_CURSOR_SORT_ASCENDING ? '<' : '>') : \
3789 	 (((EBookCacheCursor *) cursor)->sort_types[idx] == E_BOOK_CURSOR_SORT_ASCENDING ? '>' : '<'))
3790 
3791 static inline void
ebc_cursor_format_equality(EBookCache * book_cache,GString * string,EContactField field_id,const gchar * value,gchar equality)3792 ebc_cursor_format_equality (EBookCache *book_cache,
3793 			    GString *string,
3794 			    EContactField field_id,
3795 			    const gchar *value,
3796 			    gchar equality)
3797 {
3798 	SummaryField *field = summary_field_get (book_cache, field_id);
3799 
3800 	if (field &&
3801 	    (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
3802 		g_string_append (string, "summary.");
3803 		g_string_append (string, field->dbname);
3804 		g_string_append (string, "_" EBC_SUFFIX_SORT_KEY " ");
3805 
3806 		e_cache_sqlite_stmt_append_printf (string, "%c %Q", equality, value);
3807 	} else {
3808 		e_cache_sqlite_stmt_append_printf (string, "(summary." E_CACHE_COLUMN_OBJECT " %c %Q ", equality, value);
3809 
3810 		g_string_append (string, "COLLATE " EBC_COLLATE_PREFIX);
3811 		g_string_append (string, e_contact_field_name (field_id));
3812 		g_string_append_c (string, ')');
3813 	}
3814 }
3815 
3816 static gchar *
ebc_cursor_constraints(EBookCache * book_cache,EBookCacheCursor * cursor,CursorState * state,gboolean reverse,gboolean include_current_uid)3817 ebc_cursor_constraints (EBookCache *book_cache,
3818 			EBookCacheCursor *cursor,
3819 			CursorState *state,
3820 			gboolean reverse,
3821 			gboolean include_current_uid)
3822 {
3823 	GString *string;
3824 	gint ii, jj;
3825 
3826 	/* Example for:
3827 	 *    ORDER BY family_name ASC, given_name DESC
3828 	 *
3829 	 * Where current cursor values are:
3830 	 *    family_name = Jackson
3831 	 *    given_name  = Micheal
3832 	 *
3833 	 * With reverse = FALSE
3834 	 *
3835 	 *    (summary.family_name > 'Jackson') OR
3836 	 *    (summary.family_name = 'Jackson' AND summary.given_name < 'Micheal') OR
3837 	 *    (summary.family_name = 'Jackson' AND summary.given_name = 'Micheal' AND summary.uid > 'last-uid')
3838 	 *
3839 	 * With reverse = TRUE (needed for moving the cursor backwards through results)
3840 	 *
3841 	 *    (summary.family_name < 'Jackson') OR
3842 	 *    (summary.family_name = 'Jackson' AND summary.given_name > 'Micheal') OR
3843 	 *    (summary.family_name = 'Jackson' AND summary.given_name = 'Micheal' AND summary.uid < 'last-uid')
3844 	 *
3845 	 */
3846 	string = g_string_new (NULL);
3847 
3848 	for (ii = 0; ii <= cursor->n_sort_fields; ii++) {
3849 		/* Break once we hit a NULL value */
3850 		if ((ii < cursor->n_sort_fields && state->values[ii] == NULL) ||
3851 		    (ii == cursor->n_sort_fields && state->last_uid == NULL))
3852 			break;
3853 
3854 		/* Between each qualifier, add an 'OR' */
3855 		if (ii > 0)
3856 			g_string_append (string, " OR ");
3857 
3858 		/* Begin qualifier */
3859 		g_string_append_c (string, '(');
3860 
3861 		/* Create the '=' statements leading up to the current tie breaker */
3862 		for (jj = 0; jj < ii; jj++) {
3863 			ebc_cursor_format_equality (book_cache, string,
3864 						    cursor->sort_fields[jj],
3865 						    state->values[jj], '=');
3866 			g_string_append (string, " AND ");
3867 		}
3868 
3869 		if (ii == cursor->n_sort_fields) {
3870 			/* The 'include_current_uid' clause is used for calculating
3871 			 * the current position of the cursor, inclusive of the
3872 			 * current position.
3873 			 */
3874 			if (include_current_uid)
3875 				g_string_append_c (string, '(');
3876 
3877 			/* Append the UID tie breaker */
3878 			e_cache_sqlite_stmt_append_printf (
3879 				string,
3880 				"summary." E_CACHE_COLUMN_UID " %c %Q",
3881 				reverse ? '<' : '>',
3882 				state->last_uid);
3883 
3884 			if (include_current_uid)
3885 				e_cache_sqlite_stmt_append_printf (
3886 					string,
3887 					" OR summary." E_CACHE_COLUMN_UID " = %Q)",
3888 					state->last_uid);
3889 		} else {
3890 			/* SPECIAL CASE: If we have a parially set cursor state, then we must
3891 			 * report next results that are inclusive of the final qualifier.
3892 			 *
3893 			 * This allows one to set the cursor with the family name set to 'J'
3894 			 * and include the results for contact's Mr & Miss 'J'.
3895 			 */
3896 			gboolean include_exact_match =
3897 				(reverse == FALSE &&
3898 				 ((ii + 1 < cursor->n_sort_fields && state->values[ii + 1] == NULL) ||
3899 				  (ii + 1 == cursor->n_sort_fields && state->last_uid == NULL)));
3900 
3901 			if (include_exact_match)
3902 				g_string_append_c (string, '(');
3903 
3904 			/* Append the final qualifier for this field */
3905 			ebc_cursor_format_equality (book_cache, string,
3906 						    cursor->sort_fields[ii],
3907 						    state->values[ii],
3908 						    GREATER_OR_LESS (cursor, ii, reverse));
3909 
3910 			if (include_exact_match) {
3911 				g_string_append (string, " OR ");
3912 				ebc_cursor_format_equality (book_cache, string,
3913 							    cursor->sort_fields[ii],
3914 							    state->values[ii], '=');
3915 				g_string_append_c (string, ')');
3916 			}
3917 		}
3918 
3919 		/* End qualifier */
3920 		g_string_append_c (string, ')');
3921 	}
3922 
3923 	return g_string_free (string, FALSE);
3924 }
3925 
3926 static gboolean
ebc_get_int_cb(ECache * cache,gint ncols,const gchar ** column_names,const gchar ** column_values,gpointer user_data)3927 ebc_get_int_cb (ECache *cache,
3928 		gint ncols,
3929 		const gchar **column_names,
3930 		const gchar **column_values,
3931 		gpointer user_data)
3932 {
3933 	gint *pint = user_data;
3934 
3935 	g_return_val_if_fail (pint != NULL, FALSE);
3936 
3937 	if (ncols == 1) {
3938 		*pint = column_values[0] ? g_ascii_strtoll (column_values[0], NULL, 10) : 0;
3939 	} else {
3940 		*pint = 0;
3941 	}
3942 
3943 	return TRUE;
3944 }
3945 
3946 static gboolean
cursor_count_total_locked(EBookCache * book_cache,EBookCacheCursor * cursor,gint * out_total,GCancellable * cancellable,GError ** error)3947 cursor_count_total_locked (EBookCache *book_cache,
3948 			   EBookCacheCursor *cursor,
3949 			   gint *out_total,
3950 			   GCancellable *cancellable,
3951 			   GError **error)
3952 {
3953 	GString *query;
3954 	gboolean success;
3955 
3956 	query = g_string_new (cursor->select_count);
3957 
3958 	/* Add the filter constraints (if any) */
3959 	if (cursor->query) {
3960 		g_string_append (query, " WHERE ");
3961 
3962 		g_string_append_c (query, '(');
3963 		g_string_append (query, cursor->query);
3964 		g_string_append_c (query, ')');
3965 	}
3966 
3967 	/* Execute the query */
3968 	success = e_cache_sqlite_select (E_CACHE (book_cache), query->str, ebc_get_int_cb, out_total, cancellable, error);
3969 
3970 	g_string_free (query, TRUE);
3971 
3972 	return success;
3973 }
3974 
3975 static gboolean
cursor_count_position_locked(EBookCache * book_cache,EBookCacheCursor * cursor,gint * out_position,GCancellable * cancellable,GError ** error)3976 cursor_count_position_locked (EBookCache *book_cache,
3977 			      EBookCacheCursor *cursor,
3978 			      gint *out_position,
3979 			      GCancellable *cancellable,
3980 			      GError **error)
3981 {
3982 	GString *query;
3983 	gboolean success;
3984 
3985 	query = g_string_new (cursor->select_count);
3986 
3987 	/* Add the filter constraints (if any) */
3988 	if (cursor->query) {
3989 		g_string_append (query, " WHERE ");
3990 
3991 		g_string_append_c (query, '(');
3992 		g_string_append (query, cursor->query);
3993 		g_string_append_c (query, ')');
3994 	}
3995 
3996 	/* Add the cursor constraints (if any) */
3997 	if (cursor->state.values[0] != NULL) {
3998 		gchar *constraints = NULL;
3999 
4000 		if (!cursor->query)
4001 			g_string_append (query, " WHERE ");
4002 		else
4003 			g_string_append (query, " AND ");
4004 
4005 		/* Here we do a reverse query, we're looking for all the
4006 		 * results leading up to the current cursor value, including
4007 		 * the cursor value
4008 		 */
4009 		constraints = ebc_cursor_constraints (book_cache, cursor, &(cursor->state), TRUE, TRUE);
4010 
4011 		g_string_append_c (query, '(');
4012 		g_string_append (query, constraints);
4013 		g_string_append_c (query, ')');
4014 
4015 		g_free (constraints);
4016 	}
4017 
4018 	/* Execute the query */
4019 	success = e_cache_sqlite_select (E_CACHE (book_cache), query->str, ebc_get_int_cb, out_position, cancellable, error);
4020 
4021 	g_string_free (query, TRUE);
4022 
4023 	return success;
4024 }
4025 
4026 typedef struct {
4027 	gint country_code;
4028 	gchar *national;
4029 } E164Number;
4030 
4031 static E164Number *
ebc_e164_number_new(gint country_code,const gchar * national)4032 ebc_e164_number_new (gint country_code,
4033 		     const gchar *national)
4034 {
4035 	E164Number *number = g_slice_new (E164Number);
4036 
4037 	number->country_code = country_code;
4038 	number->national = g_strdup (national);
4039 
4040 	return number;
4041 }
4042 
4043 static void
ebc_e164_number_free(E164Number * number)4044 ebc_e164_number_free (E164Number *number)
4045 {
4046 	if (number) {
4047 		g_free (number->national);
4048 		g_slice_free (E164Number, number);
4049 	}
4050 }
4051 
4052 static gint
ebc_e164_number_find(E164Number * number_a,E164Number * number_b)4053 ebc_e164_number_find (E164Number *number_a,
4054 		      E164Number *number_b)
4055 {
4056 	gint ret;
4057 
4058 	ret = number_a->country_code - number_b->country_code;
4059 
4060 	if (ret == 0) {
4061 		ret = g_strcmp0 (
4062 			number_a->national,
4063 			number_b->national);
4064 	}
4065 
4066 	return ret;
4067 }
4068 
4069 static GList *
extract_e164_attribute_params(EContact * contact)4070 extract_e164_attribute_params (EContact *contact)
4071 {
4072 	EVCard *vcard = E_VCARD (contact);
4073 	GList *extracted = NULL;
4074 	GList *attr_list;
4075 
4076 	for (attr_list = e_vcard_get_attributes (vcard); attr_list; attr_list = attr_list->next) {
4077 		EVCardAttribute *const attr = attr_list->data;
4078 		EVCardAttributeParam *param = NULL;
4079 		GList *param_list, *values, *l;
4080 		gchar *this_national = NULL;
4081 		gint this_country = 0;
4082 
4083 		/* We only attach E164 parameters to TEL attributes. */
4084 		if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
4085 			continue;
4086 
4087 		/* Find already exisiting parameter, so that we can reuse it. */
4088 		for (param_list = e_vcard_attribute_get_params (attr); param_list; param_list = param_list->next) {
4089 			if (strcmp (e_vcard_attribute_param_get_name (param_list->data), EVC_X_E164) == 0) {
4090 				param = param_list->data;
4091 				break;
4092 			}
4093 		}
4094 
4095 		if (!param)
4096 			continue;
4097 
4098 		values = e_vcard_attribute_param_get_values (param);
4099 		for (l = values; l; l = l->next) {
4100 			const gchar *value = l->data;
4101 
4102 			if (value[0] == '+')
4103 				this_country = g_ascii_strtoll (&value[1], NULL, 10);
4104 			else if (this_national == NULL)
4105 				this_national = g_strdup (value);
4106 		}
4107 
4108 		if (this_national) {
4109 			E164Number *number;
4110 
4111 			number = ebc_e164_number_new (this_country, this_national);
4112 			extracted = g_list_prepend (extracted, number);
4113 		}
4114 
4115 		g_free (this_national);
4116 
4117 		/* Clear the values, we'll insert new ones */
4118 		e_vcard_attribute_param_remove_values (param);
4119 		e_vcard_attribute_remove_param (attr, EVC_X_E164);
4120 	}
4121 
4122 	return extracted;
4123 }
4124 
4125 static gboolean
update_e164_attribute_params(EBookCache * book_cache,EContact * contact,const gchar * default_region)4126 update_e164_attribute_params (EBookCache *book_cache,
4127 			      EContact *contact,
4128 			      const gchar *default_region)
4129 {
4130 	GList *original_numbers = NULL;
4131 	GList *attr_list;
4132 	gboolean changed = FALSE;
4133 	gint n_numbers = 0;
4134 	EVCard *vcard = E_VCARD (contact);
4135 
4136 	original_numbers = extract_e164_attribute_params (contact);
4137 
4138 	for (attr_list = e_vcard_get_attributes (vcard); attr_list; attr_list = attr_list->next) {
4139 		EVCardAttribute *const attr = attr_list->data;
4140 		EVCardAttributeParam *param = NULL;
4141 		const gchar *original_number = NULL;
4142 		gchar *country_string;
4143 		GList *values;
4144 		E164Number number = { 0, NULL };
4145 
4146 		/* We only attach E164 parameters to TEL attributes. */
4147 		if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
4148 			continue;
4149 
4150 		/* Fetch the TEL value */
4151 		values = e_vcard_attribute_get_values (attr);
4152 
4153 		/* Compute E164 number based on the TEL value */
4154 		if (values && values->data) {
4155 			original_number = (const gchar *) values->data;
4156 			number.national = convert_phone (original_number, book_cache->priv->region_code, &(number.country_code));
4157 		}
4158 
4159 		if (number.national == NULL)
4160 			continue;
4161 
4162 		/* Count how many we successfully parsed in this region code */
4163 		n_numbers++;
4164 
4165 		/* Check if we have a differing e164 number, if there is no match
4166 		 * in the old existing values then the vcard changed
4167 		 */
4168 		if (!g_list_find_custom (original_numbers, &number, (GCompareFunc) ebc_e164_number_find))
4169 			changed = TRUE;
4170 
4171 		if (number.country_code != 0)
4172 			country_string = g_strdup_printf ("+%d", number.country_code);
4173 		else
4174 			country_string = g_strdup ("");
4175 
4176 		param = e_vcard_attribute_param_new (EVC_X_E164);
4177 		e_vcard_attribute_add_param (attr, param);
4178 
4179 		/* Assign the parameter values. It seems odd that we revert
4180 		 * the order of NN and CC, but at least EVCard's parser doesn't
4181 		 * permit an empty first param value. Which of course could be
4182 		 * fixed - in order to create a nice potential IOP problem with
4183 		 ** other vCard parsers. */
4184 		e_vcard_attribute_param_add_values (param, number.national, country_string, NULL);
4185 
4186 		g_free (number.national);
4187 		g_free (country_string);
4188 	}
4189 
4190 	if (!changed && n_numbers != g_list_length (original_numbers))
4191 		changed = TRUE;
4192 
4193 	g_list_free_full (original_numbers, (GDestroyNotify) ebc_e164_number_free);
4194 
4195 	return changed;
4196 }
4197 
4198 static gboolean
e_book_cache_get_string(ECache * cache,gint ncols,const gchar ** column_names,const gchar ** column_values,gpointer user_data)4199 e_book_cache_get_string (ECache *cache,
4200 			 gint ncols,
4201 			 const gchar **column_names,
4202 			 const gchar **column_values,
4203 			 gpointer user_data)
4204 {
4205 	gchar **pvalue = user_data;
4206 
4207 	g_return_val_if_fail (ncols == 1, FALSE);
4208 	g_return_val_if_fail (column_names != NULL, FALSE);
4209 	g_return_val_if_fail (column_values != NULL, FALSE);
4210 	g_return_val_if_fail (pvalue != NULL, FALSE);
4211 
4212 	if (!*pvalue)
4213 		*pvalue = g_strdup (column_values[0]);
4214 
4215 	return TRUE;
4216 }
4217 
4218 static gboolean
e_book_cache_get_strings(ECache * cache,gint ncols,const gchar ** column_names,const gchar ** column_values,gpointer user_data)4219 e_book_cache_get_strings (ECache *cache,
4220 			  gint ncols,
4221 			  const gchar **column_names,
4222 			  const gchar **column_values,
4223 			  gpointer user_data)
4224 {
4225 	GSList **pvalues = user_data;
4226 
4227 	g_return_val_if_fail (ncols == 1, FALSE);
4228 	g_return_val_if_fail (column_names != NULL, FALSE);
4229 	g_return_val_if_fail (column_values != NULL, FALSE);
4230 	g_return_val_if_fail (pvalues != NULL, FALSE);
4231 
4232 	*pvalues = g_slist_prepend (*pvalues, g_strdup (column_values[0]));
4233 
4234 	return TRUE;
4235 }
4236 
4237 static gboolean
e_book_cache_get_uint64_cb(ECache * cache,gint ncols,const gchar ** column_names,const gchar ** column_values,gpointer user_data)4238 e_book_cache_get_uint64_cb (ECache *cache,
4239 			    gint ncols,
4240 			    const gchar **column_names,
4241 			    const gchar **column_values,
4242 			    gpointer user_data)
4243 {
4244 	guint64 *pui64 = user_data;
4245 
4246 	g_return_val_if_fail (pui64 != NULL, FALSE);
4247 
4248 	if (ncols == 1) {
4249 		*pui64 = column_values[0] ? g_ascii_strtoull (column_values[0], NULL, 10) : 0;
4250 	} else {
4251 		*pui64 = 0;
4252 	}
4253 
4254 	return TRUE;
4255 }
4256 
4257 static gboolean
e_book_cache_get_old_contacts_cb(ECache * cache,gint ncols,const gchar * column_names[],const gchar * column_values[],gpointer user_data)4258 e_book_cache_get_old_contacts_cb (ECache *cache,
4259 				  gint ncols,
4260 				  const gchar *column_names[],
4261 				  const gchar *column_values[],
4262 				  gpointer user_data)
4263 {
4264 	GSList **pold_contacts = user_data;
4265 
4266 	g_return_val_if_fail (pold_contacts != NULL, FALSE);
4267 	g_return_val_if_fail (ncols == 3, FALSE);
4268 
4269 	if (column_values[0] && column_values[1]) {
4270 		*pold_contacts = g_slist_prepend (*pold_contacts,
4271 			e_book_cache_search_data_new (column_values[0], column_values[1], column_values[2]));
4272 	}
4273 
4274 	return TRUE;
4275 }
4276 
4277 static gboolean
e_book_cache_gather_table_names_cb(ECache * cache,gint ncols,const gchar * column_names[],const gchar * column_values[],gpointer user_data)4278 e_book_cache_gather_table_names_cb (ECache *cache,
4279 				    gint ncols,
4280 				    const gchar *column_names[],
4281 				    const gchar *column_values[],
4282 				    gpointer user_data)
4283 {
4284 	GSList **ptables = user_data;
4285 
4286 	g_return_val_if_fail (ptables != NULL, FALSE);
4287 	g_return_val_if_fail (ncols == 1, FALSE);
4288 
4289 	*ptables = g_slist_prepend (*ptables, g_strdup (column_values[0]));
4290 
4291 	return TRUE;
4292 }
4293 
4294 static gboolean
e_book_cache_fill_pgp_cert_column(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,EOfflineState offline_state,gint ncols,const gchar * column_names[],const gchar * column_values[],gchar ** out_revision,gchar ** out_object,EOfflineState * out_offline_state,ECacheColumnValues ** out_other_columns,gpointer user_data)4295 e_book_cache_fill_pgp_cert_column (ECache *cache,
4296 				   const gchar *uid,
4297 				   const gchar *revision,
4298 				   const gchar *object,
4299 				   EOfflineState offline_state,
4300 				   gint ncols,
4301 				   const gchar *column_names[],
4302 				   const gchar *column_values[],
4303 				   gchar **out_revision,
4304 				   gchar **out_object,
4305 				   EOfflineState *out_offline_state,
4306 				   ECacheColumnValues **out_other_columns,
4307 				   gpointer user_data)
4308 {
4309 	EContact *contact;
4310 	EContactCert *cert;
4311 
4312 	g_return_val_if_fail (object != NULL, FALSE);
4313 	g_return_val_if_fail (out_other_columns != NULL, FALSE);
4314 
4315 	contact = e_contact_new_from_vcard (object);
4316 	if (!contact)
4317 		return TRUE;
4318 
4319 	*out_other_columns = e_cache_column_values_new ();
4320 	cert = e_contact_get (contact, E_CONTACT_PGP_CERT);
4321 
4322 	e_cache_column_values_take_value (*out_other_columns, e_contact_field_name (E_CONTACT_PGP_CERT), g_strdup_printf ("%d", cert ? 1 : 0));
4323 
4324 	e_contact_cert_free (cert);
4325 	g_object_unref (contact);
4326 
4327 	return TRUE;
4328 }
4329 
4330 static gboolean
e_book_cache_migrate(ECache * cache,gint from_version,GCancellable * cancellable,GError ** error)4331 e_book_cache_migrate (ECache *cache,
4332 		      gint from_version,
4333 		      GCancellable *cancellable,
4334 		      GError **error)
4335 {
4336 	EBookCache *book_cache = E_BOOK_CACHE (cache);
4337 	gboolean success = TRUE;
4338 
4339 	/* Migration from EBookSqlite database */
4340 	if (from_version <= 0) {
4341 		GSList *tables = NULL, *old_contacts = NULL, *link;
4342 
4343 		if (e_cache_sqlite_select (cache, "SELECT uid,vcard,bdata FROM folder_id ORDER BY uid",
4344 			e_book_cache_get_old_contacts_cb, &old_contacts, cancellable, NULL)) {
4345 
4346 			old_contacts = g_slist_reverse (old_contacts);
4347 
4348 			for (link = old_contacts; link && success; link = g_slist_next (link)) {
4349 				EBookCacheSearchData *data = link->data;
4350 				EContact *contact;
4351 
4352 				if (!data)
4353 					continue;
4354 
4355 				contact = e_contact_new_from_vcard_with_uid (data->vcard, data->uid);
4356 				if (!contact)
4357 					continue;
4358 
4359 				success = e_book_cache_put_contact (book_cache, contact, data->extra, 0, E_CACHE_IS_ONLINE, cancellable, error);
4360 			}
4361 		}
4362 
4363 		/* Delete obsolete tables */
4364 		success = success && e_cache_sqlite_select (cache,
4365 			"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'folder_id%'",
4366 			e_book_cache_gather_table_names_cb, &tables, cancellable, error);
4367 
4368 		for (link = tables; link && success; link = g_slist_next (link)) {
4369 			const gchar *name = link->data;
4370 			gchar *stmt;
4371 
4372 			if (!name)
4373 				continue;
4374 
4375 			stmt = e_cache_sqlite_stmt_printf ("DROP TABLE IF EXISTS %Q", name);
4376 			success = e_cache_sqlite_exec (cache, stmt, cancellable, error);
4377 			e_cache_sqlite_stmt_free (stmt);
4378 		}
4379 
4380 		g_slist_free_full (tables, g_free);
4381 
4382 		success = success && e_cache_sqlite_exec (cache, "DROP TABLE IF EXISTS keys", cancellable, error);
4383 		success = success && e_cache_sqlite_exec (cache, "DROP TABLE IF EXISTS folders", cancellable, error);
4384 		success = success && e_cache_sqlite_exec (cache, "DROP TABLE IF EXISTS folder_id", cancellable, error);
4385 
4386 		if (success) {
4387 			/* Save the changes by finishing the transaction */
4388 			e_cache_unlock (cache, E_CACHE_UNLOCK_COMMIT);
4389 			e_cache_lock (cache, E_CACHE_LOCK_WRITE);
4390 
4391 			/* Try to vacuum, but do not claim any error if failed */
4392 			e_cache_sqlite_maybe_vacuum (cache, cancellable, NULL);
4393 		}
4394 
4395 		g_slist_free_full (old_contacts, e_book_cache_search_data_free);
4396 	}
4397 
4398 	/* Add any version-related changes here */
4399 	if (success && from_version > 0 && from_version < E_BOOK_CACHE_VERSION) {
4400 		if (from_version == 1) {
4401 			/* Version 2 added E_CONTACT_PGP_CERT existence into the summary */
4402 			success = e_cache_foreach_update (cache, E_CACHE_INCLUDE_DELETED, NULL, e_book_cache_fill_pgp_cert_column, NULL, cancellable, error);
4403 		}
4404 	}
4405 
4406 	return success;
4407 }
4408 
4409 static gboolean
e_book_cache_populate_other_columns(EBookCache * book_cache,ESourceBackendSummarySetup * setup,GSList ** out_columns,GError ** error)4410 e_book_cache_populate_other_columns (EBookCache *book_cache,
4411 				     ESourceBackendSummarySetup *setup,
4412 				     GSList **out_columns, /* ECacheColumnInfo * */
4413 				     GError **error)
4414 {
4415 	GSList *columns = NULL;
4416 	gboolean use_default;
4417 	gboolean success = TRUE;
4418 	gint ii;
4419 
4420 	g_return_val_if_fail (out_columns != NULL, FALSE);
4421 
4422 	#define add_column(_name, _type, _index_name) G_STMT_START { \
4423 		columns = g_slist_prepend (columns, e_cache_column_info_new (_name, _type, _index_name)); \
4424 		} G_STMT_END
4425 
4426 	add_column (EBC_COLUMN_EXTRA, "TEXT", NULL);
4427 	add_column (EBC_COLUMN_CUSTOM_FLAGS, "INTEGER", NULL);
4428 
4429 	use_default = !setup;
4430 
4431 	if (setup) {
4432 		EContactField *fields;
4433 		EContactField *indexed_fields;
4434 		EBookIndexType *index_types = NULL;
4435 		gint n_fields = 0, n_indexed_fields = 0, ii;
4436 
4437 		fields = e_source_backend_summary_setup_get_summary_fields (setup, &n_fields);
4438 		indexed_fields = e_source_backend_summary_setup_get_indexed_fields (setup, &index_types, &n_indexed_fields);
4439 
4440 		if (n_fields <= 0 || n_fields >= EBC_MAX_SUMMARY_FIELDS) {
4441 			if (n_fields)
4442 				g_warning ("EBookCache refused to create cache with more than %d summary fields", EBC_MAX_SUMMARY_FIELDS);
4443 			use_default = TRUE;
4444 		} else {
4445 			GArray *summary_fields;
4446 
4447 			summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
4448 
4449 			/* Ensure the non-optional fields first */
4450 			summary_field_append (summary_fields, E_CONTACT_UID, error);
4451 			summary_field_append (summary_fields, E_CONTACT_REV, error);
4452 
4453 			for (ii = 0; ii < n_fields; ii++) {
4454 				if (!summary_field_append (summary_fields, fields[ii], error)) {
4455 					success = FALSE;
4456 					break;
4457 				}
4458 			}
4459 
4460 			if (!success) {
4461 				gint n_sfields;
4462 				SummaryField *sfields;
4463 
4464 				/* Properly free the array */
4465 				n_sfields = summary_fields->len;
4466 				sfields = (SummaryField *) g_array_free (summary_fields, FALSE);
4467 				summary_fields_array_free (sfields, n_sfields);
4468 
4469 				g_free (fields);
4470 				g_free (index_types);
4471 				g_free (indexed_fields);
4472 
4473 				g_slist_free_full (columns, e_cache_column_info_free);
4474 
4475 				return FALSE;
4476 			}
4477 
4478 			/* Add the 'indexed' flag to the SummaryField structs */
4479 			summary_fields_add_indexes (summary_fields, indexed_fields, index_types, n_indexed_fields);
4480 
4481 			book_cache->priv->n_summary_fields = summary_fields->len;
4482 			book_cache->priv->summary_fields = (SummaryField *) g_array_free (summary_fields, FALSE);
4483 		}
4484 
4485 		g_free (fields);
4486 		g_free (index_types);
4487 		g_free (indexed_fields);
4488 	}
4489 
4490 	if (use_default) {
4491 		GArray *summary_fields;
4492 
4493 		g_warn_if_fail (book_cache->priv->n_summary_fields == 0);
4494 
4495 		/* Create the default summary structs */
4496 		summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
4497 		for (ii = 0; ii < G_N_ELEMENTS (default_summary_fields); ii++) {
4498 			summary_field_append (summary_fields, default_summary_fields[ii], NULL);
4499 		}
4500 
4501 		/* Add the default index flags */
4502 		summary_fields_add_indexes (
4503 			summary_fields,
4504 			default_indexed_fields,
4505 			default_index_types,
4506 			G_N_ELEMENTS (default_indexed_fields));
4507 
4508 		book_cache->priv->n_summary_fields = summary_fields->len;
4509 		book_cache->priv->summary_fields = (SummaryField *) g_array_free (summary_fields, FALSE);
4510 	}
4511 
4512 	#undef add_column
4513 
4514 	if (success) {
4515 		for (ii = 0; ii < book_cache->priv->n_summary_fields; ii++) {
4516 			SummaryField *fld = &(book_cache->priv->summary_fields[ii]);
4517 
4518 			summary_field_init_dbnames (fld);
4519 
4520 			if (fld->type != E_TYPE_CONTACT_ATTR_LIST)
4521 				summary_field_prepend_columns (fld, &columns);
4522 		}
4523 	}
4524 
4525 	*out_columns = columns;
4526 
4527 	return success;
4528 }
4529 
4530 static gboolean
e_book_cache_initialize(EBookCache * book_cache,const gchar * filename,ESource * source,ESourceBackendSummarySetup * setup,GCancellable * cancellable,GError ** error)4531 e_book_cache_initialize (EBookCache *book_cache,
4532 			 const gchar *filename,
4533 			 ESource *source,
4534 			 ESourceBackendSummarySetup *setup,
4535 			 GCancellable *cancellable,
4536 			 GError **error)
4537 {
4538 	ECache *cache;
4539 	GSList *other_columns = NULL;
4540 	sqlite3 *db;
4541 	gint ii, sqret;
4542 	gboolean success;
4543 
4544 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
4545 	g_return_val_if_fail (filename != NULL, FALSE);
4546 	if (source)
4547 		g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
4548 	if (setup)
4549 		g_return_val_if_fail (E_IS_SOURCE_BACKEND_SUMMARY_SETUP (setup), FALSE);
4550 
4551 	if (source)
4552 		book_cache->priv->source = g_object_ref (source);
4553 
4554 	cache = E_CACHE (book_cache);
4555 
4556 	success = e_book_cache_populate_other_columns (book_cache, setup, &other_columns, error);
4557 	if (!success)
4558 		goto exit;
4559 
4560 	success = e_cache_initialize_sync (cache, filename, other_columns, cancellable, error);
4561 	if (!success)
4562 		goto exit;
4563 
4564 	e_cache_lock (cache, E_CACHE_LOCK_WRITE);
4565 
4566 	db = e_cache_get_sqlitedb (cache);
4567 	sqret = SQLITE_OK;
4568 
4569 	/* Install our custom functions */
4570 	for (ii = 0; sqret == SQLITE_OK && ii < G_N_ELEMENTS (ebc_custom_functions); ii++) {
4571 		sqret = sqlite3_create_function (
4572 			db,
4573 			ebc_custom_functions[ii].name,
4574 			ebc_custom_functions[ii].arguments,
4575 			SQLITE_UTF8, book_cache,
4576 			ebc_custom_functions[ii].func,
4577 			NULL, NULL);
4578 	}
4579 
4580 	/* Fallback COLLATE implementations generated on demand */
4581 	if (sqret == SQLITE_OK)
4582 		sqret = sqlite3_collation_needed (db, book_cache, ebc_generate_collator);
4583 
4584 	if (sqret != SQLITE_OK) {
4585 		if (!db) {
4586 			g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_LOAD, _("Insufficient memory"));
4587 		} else {
4588 			const gchar *errmsg = sqlite3_errmsg (db);
4589 
4590 			g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_ENGINE, _("Can’t open database %s: %s"), filename, errmsg);
4591 		}
4592 
4593 		success = FALSE;
4594 	}
4595 
4596 	success = success && ebc_init_locale (book_cache, cancellable, error);
4597 
4598 	success = success && ebc_init_aux_tables (book_cache, cancellable, error);
4599 
4600 	/* Check for data migration */
4601 	success = success && e_book_cache_migrate (cache, e_cache_get_version (cache), cancellable, error);
4602 
4603 	e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
4604 
4605 	if (!success)
4606 		goto exit;
4607 
4608 	if (e_cache_get_version (cache) != E_BOOK_CACHE_VERSION)
4609 		e_cache_set_version (cache, E_BOOK_CACHE_VERSION);
4610 
4611  exit:
4612 	g_slist_free_full (other_columns, e_cache_column_info_free);
4613 
4614 	return success;
4615 }
4616 
4617 /**
4618  * e_book_cache_new:
4619  * @filename: file name to load or create the new cache
4620  * @source: (nullable): an optional #ESource, associated with the #EBookCache, or %NULL
4621  * @cancellable: optional #GCancellable object, or %NULL
4622  * @error: return location for a #GError, or %NULL
4623  *
4624  * Creates a new #EBookCache with the default summary configuration.
4625  *
4626  * Aside from the mandatory fields %E_CONTACT_UID, %E_CONTACT_REV,
4627  * the default configuration stores the following fields for quick
4628  * performance of searches: %E_CONTACT_FILE_AS, %E_CONTACT_NICKNAME,
4629  * %E_CONTACT_FULL_NAME, %E_CONTACT_GIVEN_NAME, %E_CONTACT_FAMILY_NAME,
4630  * %E_CONTACT_EMAIL, %E_CONTACT_TEL, %E_CONTACT_IS_LIST, %E_CONTACT_LIST_SHOW_ADDRESSES,
4631  * and %E_CONTACT_WANTS_HTML.
4632  *
4633  * The fields %E_CONTACT_FULL_NAME and %E_CONTACT_EMAIL are configured
4634  * to respond extra quickly with the %E_BOOK_INDEX_PREFIX index flag.
4635  *
4636  * The fields %E_CONTACT_FILE_AS, %E_CONTACT_FAMILY_NAME and
4637  * %E_CONTACT_GIVEN_NAME are configured to perform well with
4638  * the #EBookCacheCursor, using the %E_BOOK_INDEX_SORT_KEY
4639  * index flag.
4640  *
4641  * Returns: (transfer full) (nullable): A new #EBookCache or %NULL on error
4642  *
4643  * Since: 3.26
4644  **/
4645 EBookCache *
e_book_cache_new(const gchar * filename,ESource * source,GCancellable * cancellable,GError ** error)4646 e_book_cache_new (const gchar *filename,
4647 		  ESource *source,
4648 		  GCancellable *cancellable,
4649 		  GError **error)
4650 {
4651 	g_return_val_if_fail (filename != NULL, NULL);
4652 
4653 	return e_book_cache_new_full (filename, source, NULL, cancellable, error);
4654 }
4655 
4656 /**
4657  * e_book_cache_new_full:
4658  * @filename: file name to load or create the new cache
4659  * @source: (nullable): an optional #ESource, associated with the #EBookCache, or %NULL
4660  * @setup: (nullable): an #ESourceBackendSummarySetup describing how the summary should be setup, or %NULL to use the default
4661  * @cancellable: optional #GCancellable object, or %NULL
4662  * @error: return location for a #GError, or %NULL
4663  *
4664  * Creates a new #EBookCache with the given or the default summary configuration.
4665  *
4666  * Like e_book_sqlite_new(), but allows configuration of which contact fields
4667  * will be stored for quick reference in the summary. The configuration indicated by
4668  * @setup will only be taken into account when initially creating the underlying table,
4669  * further configurations will be ignored.
4670  *
4671  * The fields %E_CONTACT_UID and %E_CONTACT_REV are not optional,
4672  * they will be stored in the summary regardless of this function's parameters.
4673  * Only #EContactFields with the type %G_TYPE_STRING, %G_TYPE_BOOLEAN or
4674  * %E_TYPE_CONTACT_ATTR_LIST are currently supported.
4675  *
4676  * Returns: (transfer full) (nullable): A new #EBookCache or %NULL on error
4677  *
4678  * Since: 3.26
4679  **/
4680 EBookCache *
e_book_cache_new_full(const gchar * filename,ESource * source,ESourceBackendSummarySetup * setup,GCancellable * cancellable,GError ** error)4681 e_book_cache_new_full (const gchar *filename,
4682 		       ESource *source,
4683 		       ESourceBackendSummarySetup *setup,
4684 		       GCancellable *cancellable,
4685 		       GError **error)
4686 {
4687 	EBookCache *book_cache;
4688 
4689 	g_return_val_if_fail (filename != NULL, NULL);
4690 
4691 	book_cache = g_object_new (E_TYPE_BOOK_CACHE, NULL);
4692 
4693 	if (!e_book_cache_initialize (book_cache, filename, source, setup, cancellable, error)) {
4694 		g_object_unref (book_cache);
4695 		book_cache = NULL;
4696 	}
4697 
4698 	return book_cache;
4699 }
4700 
4701 /**
4702  * e_book_cache_ref_source:
4703  * @book_cache: An #EBookCache
4704  *
4705  * References the #ESource to which @book_cache is paired,
4706  * use g_object_unref() when no longer needed.
4707  * It can be %NULL in some cases, like when running tests.
4708  *
4709  * Returns: (transfer full): A reference to the #ESource to which @book_cache
4710  *    is paired, or %NULL.
4711  *
4712  * Since: 3.26
4713  **/
4714 ESource *
e_book_cache_ref_source(EBookCache * book_cache)4715 e_book_cache_ref_source (EBookCache *book_cache)
4716 {
4717 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
4718 
4719 	if (book_cache->priv->source)
4720 		return g_object_ref (book_cache->priv->source);
4721 
4722 	return NULL;
4723 }
4724 
4725 /**
4726  * e_book_cache_dup_contact_revision:
4727  * @book_cache: an #EBookCache
4728  * @contact: an #EContact
4729  *
4730  * Returns the @contact revision, used to detect changes.
4731  * The returned string should be freed with g_free(), when
4732  * no longer needed.
4733  *
4734  * Returns: (transfer full): A newly allocated string containing
4735  *    revision of the @contact.
4736  *
4737  * Since: 3.26
4738  **/
4739 gchar *
e_book_cache_dup_contact_revision(EBookCache * book_cache,EContact * contact)4740 e_book_cache_dup_contact_revision (EBookCache *book_cache,
4741 				   EContact *contact)
4742 {
4743 	gchar *revision = NULL;
4744 
4745 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
4746 	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
4747 
4748 	g_signal_emit (book_cache, signals[DUP_CONTACT_REVISION], 0, contact, &revision);
4749 
4750 	return revision;
4751 }
4752 
4753 /**
4754  * e_book_cache_set_locale:
4755  * @book_cache: An #EBookCache
4756  * @lc_collate: The new locale for the cache
4757  * @cancellable: optional #GCancellable object, or %NULL
4758  * @error: return location for a #GError, or %NULL
4759  *
4760  * Relocalizes any locale specific data in the specified
4761  * new @lc_collate locale.
4762  *
4763  * The @lc_collate locale setting is stored and remembered on
4764  * subsequent accesses of the cache, changing the locale will
4765  * store the new locale and will modify sort keys and any
4766  * locale specific data in the cache.
4767  *
4768  * As a side effect, it's possible that changing the locale
4769  * will cause stored vCard-s to change.
4770  *
4771  * Returns: Whether the new locale was successfully set.
4772  *
4773  * Since: 3.26
4774  **/
4775 gboolean
e_book_cache_set_locale(EBookCache * book_cache,const gchar * lc_collate,GCancellable * cancellable,GError ** error)4776 e_book_cache_set_locale (EBookCache *book_cache,
4777 			 const gchar *lc_collate,
4778 			 GCancellable *cancellable,
4779 			 GError **error)
4780 {
4781 	ECache *cache;
4782 	gboolean success, changed = FALSE;
4783 	gchar *stored_lc_collate = NULL;
4784 
4785 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
4786 
4787 	cache = E_CACHE (book_cache);
4788 
4789 	e_cache_lock (cache, E_CACHE_LOCK_WRITE);
4790 
4791 	success = ebc_set_locale_internal (book_cache, lc_collate, error);
4792 
4793 	if (success)
4794 		stored_lc_collate = e_cache_dup_key (cache, EBC_KEY_LC_COLLATE, NULL);
4795 
4796 	if (success && g_strcmp0 (stored_lc_collate, lc_collate) != 0)
4797 		success = ebc_upgrade (book_cache, cancellable, error);
4798 
4799 	/* If for some reason we failed, then reset the collator to use the old locale */
4800 	if (!success && stored_lc_collate && stored_lc_collate[0]) {
4801 		ebc_set_locale_internal (book_cache, stored_lc_collate, NULL);
4802 		changed = TRUE;
4803 	}
4804 
4805 	e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
4806 
4807 	g_free (stored_lc_collate);
4808 
4809 	if (success || changed)
4810 		g_object_notify (G_OBJECT (book_cache), "locale");
4811 
4812 	return success;
4813 }
4814 
4815 /**
4816  * e_book_cache_dup_locale:
4817  * @book_cache: An #EBookCache
4818  *
4819  * Returns: (transfer full): A new string containing the current local
4820  *    being used by the @book_cache. Free it with g_free(), when no
4821  *    longer needed.
4822  *
4823  * Since: 3.26
4824  **/
4825 gchar *
e_book_cache_dup_locale(EBookCache * book_cache)4826 e_book_cache_dup_locale (EBookCache *book_cache)
4827 {
4828 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
4829 
4830 	return g_strdup (book_cache->priv->locale);
4831 }
4832 
4833 /**
4834  * e_book_cache_ref_collator:
4835  * @book_cache: An #EBookCache
4836  *
4837  * References the currently active #ECollator for @book_cache,
4838  * use e_collator_unref() when finished using the returned collator.
4839  *
4840  * Note that the active collator will change with the active locale setting.
4841  *
4842  * Returns: (transfer full): A reference to the active collator.
4843  *
4844  * Since: 3.26
4845  **/
4846 ECollator *
e_book_cache_ref_collator(EBookCache * book_cache)4847 e_book_cache_ref_collator (EBookCache *book_cache)
4848 {
4849 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
4850 
4851 	return e_collator_ref (book_cache->priv->collator);
4852 }
4853 
4854 /**
4855  * e_book_cache_put_contact:
4856  * @book_cache: An #EBookCache
4857  * @contact: an #EContact to be added
4858  * @extra: (nullable): extra data to store in association with this @contact
4859  * @custom_flags: custom flags for the @contact, not interpreted by the @book_cache
4860  * @offline_flag: one of #ECacheOfflineFlag, whether putting this contact in offline
4861  * @cancellable: optional #GCancellable object, or %NULL
4862  * @error: return location for a #GError, or %NULL
4863  *
4864  * This is a convenience wrapper for e_book_cache_put_contacts(),
4865  * which is the preferred way to add or modify multiple contacts when possible.
4866  *
4867  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
4868  *
4869  * Since: 3.26
4870  **/
4871 gboolean
e_book_cache_put_contact(EBookCache * book_cache,EContact * contact,const gchar * extra,guint32 custom_flags,ECacheOfflineFlag offline_flag,GCancellable * cancellable,GError ** error)4872 e_book_cache_put_contact (EBookCache *book_cache,
4873 			  EContact *contact,
4874 			  const gchar *extra,
4875 			  guint32 custom_flags,
4876 			  ECacheOfflineFlag offline_flag,
4877 			  GCancellable *cancellable,
4878 			  GError **error)
4879 {
4880 	GSList *contacts, *extras, *custom_flags_lst;
4881 	gboolean success;
4882 
4883 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
4884 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
4885 
4886 	contacts = g_slist_append (NULL, contact);
4887 	extras = g_slist_append (NULL, (gpointer) extra);
4888 	custom_flags_lst = g_slist_append (NULL, GUINT_TO_POINTER (custom_flags));
4889 
4890 	success = e_book_cache_put_contacts (book_cache, contacts, extras, custom_flags_lst, offline_flag, cancellable, error);
4891 
4892 	g_slist_free (custom_flags_lst);
4893 	g_slist_free (contacts);
4894 	g_slist_free (extras);
4895 
4896 	return success;
4897 }
4898 
4899 /**
4900  * e_book_cache_put_contacts:
4901  * @book_cache: An #EBookCache
4902  * @contacts: (element-type EContact): A list of contacts to add to @book_cache
4903  * @extras: (nullable) (element-type utf8): A list of extra data to store in association with the @contacts
4904  * @custom_flags: (nullable) (element-type guint32): optional custom flags to use for the @contacts
4905  * @offline_flag: one of #ECacheOfflineFlag, whether putting these contacts in offline
4906  * @cancellable: optional #GCancellable object, or %NULL
4907  * @error: return location for a #GError, or %NULL
4908  *
4909  * Adds or replaces contacts in @book_cache.
4910  *
4911  * If @extras is specified, it must have an equal length as the @contacts list.
4912  * Similarly the non-NULL @custom_flags length should be the same as the length of the @contacts.
4913  * Each element from the @extras list and @custom_flags list will be stored in association
4914  * with its corresponding contact in the @contacts list.
4915  *
4916  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
4917  *
4918  * Since: 3.26
4919  **/
4920 gboolean
e_book_cache_put_contacts(EBookCache * book_cache,const GSList * contacts,const GSList * extras,const GSList * custom_flags,ECacheOfflineFlag offline_flag,GCancellable * cancellable,GError ** error)4921 e_book_cache_put_contacts (EBookCache *book_cache,
4922 			   const GSList *contacts,
4923 			   const GSList *extras,
4924 			   const GSList *custom_flags,
4925 			   ECacheOfflineFlag offline_flag,
4926 			   GCancellable *cancellable,
4927 			   GError **error)
4928 {
4929 	const GSList *clink, *elink, *flink;
4930 	ECache *cache;
4931 	ECacheColumnValues *other_columns;
4932 	gboolean success;
4933 
4934 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
4935 	g_return_val_if_fail (contacts != NULL, FALSE);
4936 	g_return_val_if_fail (extras == NULL || g_slist_length ((GSList *) extras) == g_slist_length ((GSList *) contacts), FALSE);
4937 	g_return_val_if_fail (custom_flags == NULL || g_slist_length ((GSList *) contacts) == g_slist_length ((GSList *) custom_flags), FALSE);
4938 
4939 	cache = E_CACHE (book_cache);
4940 	other_columns = e_cache_column_values_new ();
4941 
4942 	e_cache_lock (cache, E_CACHE_LOCK_WRITE);
4943 	e_cache_freeze_revision_change (cache);
4944 
4945 	for (clink = contacts, elink = extras, flink = custom_flags;
4946 	     clink;
4947 	     (clink = g_slist_next (clink)), (elink = g_slist_next (elink)), (flink = g_slist_next (flink))) {
4948 		EContact *contact = clink->data;
4949 		const gchar *extra = elink ? elink->data : NULL;
4950 		guint32 custom_flags_val = flink ? GPOINTER_TO_UINT (flink->data) : 0;
4951 		gchar *uid, *rev, *vcard;
4952 
4953 		g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
4954 
4955 		vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
4956 		g_return_val_if_fail (vcard != NULL, FALSE);
4957 
4958 		e_cache_column_values_remove_all (other_columns);
4959 
4960 		if (extra)
4961 			e_cache_column_values_take_value (other_columns, EBC_COLUMN_EXTRA, g_strdup (extra));
4962 		e_cache_column_values_take_value (other_columns, EBC_COLUMN_CUSTOM_FLAGS, g_strdup_printf ("%u", custom_flags_val));
4963 
4964 		uid = e_contact_get (contact, E_CONTACT_UID);
4965 		rev = e_book_cache_dup_contact_revision (book_cache, contact);
4966 
4967 		ebc_fill_other_columns (book_cache, contact, other_columns);
4968 
4969 		success = e_cache_put (cache, uid, rev, vcard, other_columns, offline_flag, cancellable, error);
4970 
4971 		g_free (vcard);
4972 		g_free (rev);
4973 		g_free (uid);
4974 
4975 		if (!success)
4976 			break;
4977 	}
4978 
4979 	e_cache_thaw_revision_change (cache);
4980 	e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
4981 
4982 	e_cache_column_values_free (other_columns);
4983 
4984 	return success;
4985 }
4986 
4987 /**
4988  * e_book_cache_remove_contact:
4989  * @book_cache: An #EBookCache
4990  * @uid: the uid of the contact to remove
4991  * @custom_flags: custom flags for the contact with the given @uid, not interpreted by the @book_cache
4992  * @offline_flag: one of #ECacheOfflineFlag, whether removing this contact in offline
4993  * @cancellable: optional #GCancellable object, or %NULL
4994  * @error: return location for a #GError, or %NULL
4995  *
4996  * Removes the contact identified by @uid from @book_cache.
4997  *
4998  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
4999  *
5000  * Since: 3.26
5001  **/
5002 gboolean
e_book_cache_remove_contact(EBookCache * book_cache,const gchar * uid,guint32 custom_flags,ECacheOfflineFlag offline_flag,GCancellable * cancellable,GError ** error)5003 e_book_cache_remove_contact (EBookCache *book_cache,
5004 			     const gchar *uid,
5005 			     guint32 custom_flags,
5006 			     ECacheOfflineFlag offline_flag,
5007 			     GCancellable *cancellable,
5008 			     GError **error)
5009 {
5010 	GSList *uids, *custom_flags_lst;
5011 	gboolean success;
5012 
5013 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5014 	g_return_val_if_fail (uid != NULL, FALSE);
5015 
5016 	uids = g_slist_append (NULL, (gpointer) uid);
5017 	custom_flags_lst = g_slist_append (NULL, GUINT_TO_POINTER (custom_flags));
5018 
5019 	success = e_book_cache_remove_contacts (book_cache, uids, custom_flags_lst, offline_flag, cancellable, error);
5020 
5021 	g_slist_free (custom_flags_lst);
5022 	g_slist_free (uids);
5023 
5024 	return success;
5025 }
5026 
5027 /**
5028  * e_book_cache_remove_contacts:
5029  * @book_cache: An #EBookCache
5030  * @uids: (element-type utf8): a #GSList of uids indicating which contacts to remove
5031  * @custom_flags: (element-type guint32) (nullable): an optional #GSList of custom flags for the @ids
5032  * @offline_flag: one of #ECacheOfflineFlag, whether removing these contacts in offline
5033  * @cancellable: optional #GCancellable object, or %NULL
5034  * @error: return location for a #GError, or %NULL
5035  *
5036  * Removes the contacts indicated by @uids from @book_cache.
5037  * The @custom_flags is used, if not %NULL, only if the @offline_flag
5038  * is %E_CACHE_IS_OFFLINE. Otherwise it's ignored. The length of
5039  * the @custom_flags should match the length of @uids, when not %NULL.
5040  *
5041  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5042  *
5043  * Since: 3.26
5044  **/
5045 gboolean
e_book_cache_remove_contacts(EBookCache * book_cache,const GSList * uids,const GSList * custom_flags,ECacheOfflineFlag offline_flag,GCancellable * cancellable,GError ** error)5046 e_book_cache_remove_contacts (EBookCache *book_cache,
5047 			      const GSList *uids,
5048 			      const GSList *custom_flags,
5049 			      ECacheOfflineFlag offline_flag,
5050 			      GCancellable *cancellable,
5051 			      GError **error)
5052 {
5053 	ECache *cache;
5054 	const GSList *link, *flink;
5055 	gboolean success = TRUE;
5056 
5057 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5058 	g_return_val_if_fail (uids != NULL, FALSE);
5059 	g_return_val_if_fail (custom_flags == NULL || g_slist_length ((GSList *) uids) == g_slist_length ((GSList *) custom_flags), FALSE);
5060 
5061 	cache = E_CACHE (book_cache);
5062 
5063 	e_cache_lock (cache, E_CACHE_LOCK_WRITE);
5064 	e_cache_freeze_revision_change (cache);
5065 
5066 	for (link = uids, flink = custom_flags; success && link; (link = g_slist_next (link)), (flink = g_slist_next (flink))) {
5067 		const gchar *uid = link->data;
5068 		guint32 custom_flags_val = flink ? GPOINTER_TO_UINT (flink->data) : 0;
5069 
5070 		if (offline_flag == E_CACHE_IS_OFFLINE && flink) {
5071 			success = e_book_cache_set_contact_custom_flags (book_cache, uid, custom_flags_val, cancellable, error);
5072 
5073 			if (!success)
5074 				break;
5075 		}
5076 
5077 		success = e_cache_remove (cache, uid, offline_flag, cancellable, error);
5078 	}
5079 
5080 	e_cache_thaw_revision_change (cache);
5081 	e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
5082 
5083 	return success;
5084 }
5085 
5086 /**
5087  * e_book_cache_get_contact:
5088  * @book_cache: An #EBookCache
5089  * @uid: The uid of the contact to fetch
5090  * @meta_contact: Whether an entire contact is desired, or only the metadata
5091  * @out_contact: (out) (transfer full): Return location to store the fetched contact
5092  * @cancellable: optional #GCancellable object, or %NULL
5093  * @error: return location for a #GError, or %NULL
5094  *
5095  * Fetch the #EContact specified by @uid in @book_cache.
5096  *
5097  * If @meta_contact is specified, then a shallow #EContact will be created
5098  * holding only the %E_CONTACT_UID and %E_CONTACT_REV fields.
5099  *
5100  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5101  *
5102  * Since: 3.26
5103  **/
5104 gboolean
e_book_cache_get_contact(EBookCache * book_cache,const gchar * uid,gboolean meta_contact,EContact ** out_contact,GCancellable * cancellable,GError ** error)5105 e_book_cache_get_contact (EBookCache *book_cache,
5106 			  const gchar *uid,
5107 			  gboolean meta_contact,
5108 			  EContact **out_contact,
5109 			  GCancellable *cancellable,
5110 			  GError **error)
5111 {
5112 	gchar *vcard = NULL;
5113 
5114 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5115 	g_return_val_if_fail (uid != NULL, FALSE);
5116 	g_return_val_if_fail (out_contact != NULL, FALSE);
5117 
5118 	*out_contact = NULL;
5119 
5120 	if (!e_book_cache_get_vcard (book_cache, uid, meta_contact, &vcard, cancellable, error) ||
5121 	    !vcard) {
5122 		return FALSE;
5123 	}
5124 
5125 	*out_contact = e_contact_new_from_vcard_with_uid (vcard, uid);
5126 
5127 	g_free (vcard);
5128 
5129 	return TRUE;
5130 }
5131 
5132 /**
5133  * e_book_cache_get_vcard:
5134  * @book_cache: An #EBookCache
5135  * @uid: The uid of the contact to fetch
5136  * @meta_contact: Whether an entire contact is desired, or only the metadata
5137  * @out_vcard: (out) (transfer full): Return location to store the fetched vCard string
5138  * @cancellable: optional #GCancellable object, or %NULL
5139  * @error: return location for a #GError, or %NULL
5140  *
5141  * Fetch a vCard string for @uid in @book_cache.
5142  *
5143  * If @meta_contact is specified, then a shallow vCard representation will be
5144  * created holding only the %E_CONTACT_UID and %E_CONTACT_REV fields.
5145  *
5146  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5147  *
5148  * Since: 3.26
5149  **/
5150 gboolean
e_book_cache_get_vcard(EBookCache * book_cache,const gchar * uid,gboolean meta_contact,gchar ** out_vcard,GCancellable * cancellable,GError ** error)5151 e_book_cache_get_vcard (EBookCache *book_cache,
5152 			const gchar *uid,
5153 			gboolean meta_contact,
5154 			gchar **out_vcard,
5155 			GCancellable *cancellable,
5156 			GError **error)
5157 {
5158 	gchar *full_vcard, *revision = NULL;
5159 
5160 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5161 	g_return_val_if_fail (uid != NULL, FALSE);
5162 	g_return_val_if_fail (out_vcard != NULL, FALSE);
5163 
5164 	*out_vcard = NULL;
5165 
5166 	full_vcard = e_cache_get (E_CACHE (book_cache), uid,
5167 		meta_contact ? &revision : NULL,
5168 		NULL, cancellable, error);
5169 
5170 	if (!full_vcard) {
5171 		g_warn_if_fail (revision == NULL);
5172 		return FALSE;
5173 	}
5174 
5175 	if (meta_contact) {
5176 		EContact *contact = e_contact_new ();
5177 
5178 		e_contact_set (contact, E_CONTACT_UID, uid);
5179 		if (revision)
5180 			e_contact_set (contact, E_CONTACT_REV, revision);
5181 
5182 		*out_vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
5183 
5184 		g_object_unref (contact);
5185 		g_free (full_vcard);
5186 	} else {
5187 		*out_vcard = full_vcard;
5188 	}
5189 
5190 	g_free (revision);
5191 
5192 	return TRUE;
5193 }
5194 
5195 /**
5196  * e_book_cache_set_contact_custom_flags:
5197  * @book_cache: an #EBookCache
5198  * @uid: The uid of the contact to set the extra data for
5199  * @custom_flags: the custom flags to set for the contact
5200  * @cancellable: optional #GCancellable object, or %NULL
5201  * @error: return location for a #GError, or %NULL
5202  *
5203  * Sets or replaces the custom flags associated with a contact
5204  * identified by the @uid.
5205  *
5206  * Returns: Whether succeeded.
5207  *
5208  * Since: 3.34
5209  **/
5210 gboolean
e_book_cache_set_contact_custom_flags(EBookCache * book_cache,const gchar * uid,guint32 custom_flags,GCancellable * cancellable,GError ** error)5211 e_book_cache_set_contact_custom_flags (EBookCache *book_cache,
5212 				       const gchar *uid,
5213 				       guint32 custom_flags,
5214 				       GCancellable *cancellable,
5215 				       GError **error)
5216 {
5217 	gchar *stmt;
5218 	gboolean success;
5219 
5220 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5221 	g_return_val_if_fail (uid != NULL, FALSE);
5222 
5223 	if (!e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_INCLUDE_DELETED)) {
5224 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not found"), uid);
5225 		return FALSE;
5226 	}
5227 
5228 	stmt = e_cache_sqlite_stmt_printf (
5229 		"UPDATE " E_CACHE_TABLE_OBJECTS " SET " EBC_COLUMN_CUSTOM_FLAGS "=%u"
5230 		" WHERE " E_CACHE_COLUMN_UID "=%Q",
5231 		custom_flags, uid);
5232 
5233 	success = e_cache_sqlite_exec (E_CACHE (book_cache), stmt, cancellable, error);
5234 
5235 	e_cache_sqlite_stmt_free (stmt);
5236 
5237 	return success;
5238 }
5239 
5240 /**
5241  * e_book_cache_get_contact_custom_flags:
5242  * @book_cache: an #EBookCache
5243  * @uid: The uid of the contact to set the extra data for
5244  * @out_custom_flags: (out): return location to store the custom flags
5245  * @cancellable: optional #GCancellable object, or %NULL
5246  * @error: return location for a #GError, or %NULL
5247  *
5248  * Gets the custom flags previously set for the @uid, either with
5249  * e_book_cache_set_contact_custom_flags(), when adding contacts or
5250  * when removing contacts in offline.
5251  *
5252  * Returns: Whether succeeded.
5253  *
5254  * Since: 3.34
5255  **/
5256 gboolean
e_book_cache_get_contact_custom_flags(EBookCache * book_cache,const gchar * uid,guint32 * out_custom_flags,GCancellable * cancellable,GError ** error)5257 e_book_cache_get_contact_custom_flags (EBookCache *book_cache,
5258 				       const gchar *uid,
5259 				       guint32 *out_custom_flags,
5260 				       GCancellable *cancellable,
5261 				       GError **error)
5262 {
5263 	gchar *stmt;
5264 	guint64 value = 0;
5265 	gboolean success;
5266 
5267 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5268 	g_return_val_if_fail (uid != NULL, FALSE);
5269 
5270 	if (!e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_INCLUDE_DELETED)) {
5271 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not found"), uid);
5272 		return FALSE;
5273 	}
5274 
5275 	stmt = e_cache_sqlite_stmt_printf (
5276 		"SELECT " EBC_COLUMN_CUSTOM_FLAGS " FROM " E_CACHE_TABLE_OBJECTS
5277 		" WHERE " E_CACHE_COLUMN_UID "=%Q",
5278 		uid);
5279 
5280 	success = e_cache_sqlite_select (E_CACHE (book_cache), stmt, e_book_cache_get_uint64_cb, &value, cancellable, error);
5281 
5282 	e_cache_sqlite_stmt_free (stmt);
5283 
5284 	if (out_custom_flags)
5285 		*out_custom_flags = (guint32) value;
5286 
5287 	return success;
5288 }
5289 
5290 /**
5291  * e_book_cache_set_contact_extra:
5292  * @book_cache: An #EBookCache
5293  * @uid: The uid of the contact to set the extra data for
5294  * @extra: (nullable): The extra data to set
5295  * @cancellable: optional #GCancellable object, or %NULL
5296  * @error: return location for a #GError, or %NULL
5297  *
5298  * Sets or replaces the extra data associated with @uid.
5299  *
5300  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5301  *
5302  * Since: 3.26
5303  **/
5304 gboolean
e_book_cache_set_contact_extra(EBookCache * book_cache,const gchar * uid,const gchar * extra,GCancellable * cancellable,GError ** error)5305 e_book_cache_set_contact_extra (EBookCache *book_cache,
5306 				const gchar *uid,
5307 				const gchar *extra,
5308 				GCancellable *cancellable,
5309 				GError **error)
5310 {
5311 	gchar *stmt;
5312 	gboolean success;
5313 
5314 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5315 	g_return_val_if_fail (uid != NULL, FALSE);
5316 
5317 	if (!e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_INCLUDE_DELETED)) {
5318 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not found"), uid);
5319 		return FALSE;
5320 	}
5321 
5322 	if (extra) {
5323 		stmt = e_cache_sqlite_stmt_printf (
5324 			"UPDATE " E_CACHE_TABLE_OBJECTS " SET " EBC_COLUMN_EXTRA "=%Q"
5325 			" WHERE " E_CACHE_COLUMN_UID "=%Q",
5326 			extra, uid);
5327 	} else {
5328 		stmt = e_cache_sqlite_stmt_printf (
5329 			"UPDATE " E_CACHE_TABLE_OBJECTS " SET " EBC_COLUMN_EXTRA "=NULL"
5330 			" WHERE " E_CACHE_COLUMN_UID "=%Q",
5331 			uid);
5332 	}
5333 
5334 	success = e_cache_sqlite_exec (E_CACHE (book_cache), stmt, cancellable, error);
5335 
5336 	e_cache_sqlite_stmt_free (stmt);
5337 
5338 	return success;
5339 }
5340 
5341 /**
5342  * e_book_cache_get_contact_extra:
5343  * @book_cache: An #EBookCache
5344  * @uid: The uid of the contact to fetch the extra data for
5345  * @out_extra: (out) (transfer full): Return location to store the extra data
5346  * @cancellable: optional #GCancellable object, or %NULL
5347  * @error: return location for a #GError, or %NULL
5348  *
5349  * Fetches the extra data previously set for @uid, either with
5350  * e_book_cache_set_contact_extra() or when adding contacts.
5351  *
5352  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5353  *
5354  * Since: 3.26
5355  **/
5356 gboolean
e_book_cache_get_contact_extra(EBookCache * book_cache,const gchar * uid,gchar ** out_extra,GCancellable * cancellable,GError ** error)5357 e_book_cache_get_contact_extra (EBookCache *book_cache,
5358 				const gchar *uid,
5359 				gchar **out_extra,
5360 				GCancellable *cancellable,
5361 				GError **error)
5362 {
5363 	gchar *stmt;
5364 	gboolean success;
5365 
5366 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5367 	g_return_val_if_fail (uid != NULL, FALSE);
5368 
5369 	if (!e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_INCLUDE_DELETED)) {
5370 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not found"), uid);
5371 		return FALSE;
5372 	}
5373 
5374 	stmt = e_cache_sqlite_stmt_printf (
5375 		"SELECT " EBC_COLUMN_EXTRA " FROM " E_CACHE_TABLE_OBJECTS
5376 		" WHERE " E_CACHE_COLUMN_UID "=%Q",
5377 		uid);
5378 
5379 	success = e_cache_sqlite_select (E_CACHE (book_cache), stmt, e_book_cache_get_string, out_extra, cancellable, error);
5380 
5381 	e_cache_sqlite_stmt_free (stmt);
5382 
5383 	return success;
5384 }
5385 
5386 /**
5387  * e_book_cache_get_uids_with_extra:
5388  * @book_cache: an #EBookCache
5389  * @extra: an extra column value to search for
5390  * @out_uids: (out) (transfer full) (element-type utf8): return location to store the UIDs to
5391  * @cancellable: optional #GCancellable object, or %NULL
5392  * @error: return location for a #GError, or %NULL
5393  *
5394  * Gets all the UID-s the @extra data is set for.
5395  *
5396  * The @out_uids should be freed with
5397  * g_slist_free_full (uids, g_free);
5398  * when no longer needed.
5399  *
5400  * Returns: Whether succeeded.
5401  *
5402  * Since: 3.26
5403  **/
5404 gboolean
e_book_cache_get_uids_with_extra(EBookCache * book_cache,const gchar * extra,GSList ** out_uids,GCancellable * cancellable,GError ** error)5405 e_book_cache_get_uids_with_extra (EBookCache *book_cache,
5406 				  const gchar *extra,
5407 				  GSList **out_uids,
5408 				  GCancellable *cancellable,
5409 				  GError **error)
5410 {
5411 	gchar *stmt;
5412 	gboolean success;
5413 
5414 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5415 	g_return_val_if_fail (extra != NULL, FALSE);
5416 	g_return_val_if_fail (out_uids != NULL, FALSE);
5417 
5418 	*out_uids = NULL;
5419 
5420 	stmt = e_cache_sqlite_stmt_printf (
5421 		"SELECT " E_CACHE_COLUMN_UID " FROM " E_CACHE_TABLE_OBJECTS
5422 		" WHERE " EBC_COLUMN_EXTRA "=%Q",
5423 		extra);
5424 
5425 	success = e_cache_sqlite_select (E_CACHE (book_cache), stmt, e_book_cache_get_strings, out_uids, cancellable, error);
5426 
5427 	e_cache_sqlite_stmt_free (stmt);
5428 
5429 	if (success && !*out_uids) {
5430 		g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object with extra “%s” not found"), extra);
5431 		success = FALSE;
5432 	} else {
5433 		*out_uids = g_slist_reverse (*out_uids);
5434 	}
5435 
5436 	return success;
5437 }
5438 
5439 /**
5440  * e_book_cache_search:
5441  * @book_cache: An #EBookCache
5442  * @sexp: (nullable): search expression; use %NULL or an empty string to list all stored contacts
5443  * @meta_contacts: Whether entire contacts are desired, or only the metadata
5444  * @out_list: (out) (transfer full) (element-type EBookCacheSearchData): Return location
5445  *    to store a #GSList of #EBookCacheSearchData structures
5446  * @cancellable: optional #GCancellable object, or %NULL
5447  * @error: return location for a #GError, or %NULL
5448  *
5449  * Searches @book_cache for contacts matching the search expression @sexp.
5450  *
5451  * When @sexp refers only to #EContactFields configured in the summary of @book_cache,
5452  * the search should always be quick, when searching for other #EContactFields
5453  * a fallback will be used.
5454  *
5455  * The returned @out_list list should be freed with g_slist_free_full (list, e_book_cache_search_data_free)
5456  * when no longer needed.
5457  *
5458  * If @meta_contact is specified, then shallow vCard representations will be
5459  * created holding only the %E_CONTACT_UID and %E_CONTACT_REV fields.
5460  *
5461  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5462  *
5463  * Since: 3.26
5464  **/
5465 gboolean
e_book_cache_search(EBookCache * book_cache,const gchar * sexp,gboolean meta_contacts,GSList ** out_list,GCancellable * cancellable,GError ** error)5466 e_book_cache_search (EBookCache *book_cache,
5467 		     const gchar *sexp,
5468 		     gboolean meta_contacts,
5469 		     GSList **out_list,
5470 		     GCancellable *cancellable,
5471 		     GError **error)
5472 {
5473 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5474 	g_return_val_if_fail (out_list != NULL, FALSE);
5475 
5476 	*out_list = NULL;
5477 
5478 	return ebc_search_internal (book_cache, sexp,
5479 		meta_contacts ? SEARCH_UID_AND_REV : SEARCH_FULL,
5480 		out_list, NULL, NULL, cancellable, error);
5481 }
5482 
5483 /**
5484  * e_book_cache_search_uids:
5485  * @book_cache: An #EBookCache
5486  * @sexp: (nullable): search expression; use %NULL or an empty string to get all stored contacts
5487  * @out_list: (out) (transfer full) (element-type utf8): Return location to store a #GSList of contact uids
5488  * @cancellable: optional #GCancellable object, or %NULL
5489  * @error: return location for a #GError, or %NULL
5490  *
5491  * Similar to e_book_cache_search(), but fetches only a list of contact UIDs.
5492  *
5493  * The returned @out_list list should be freed with g_slist_free_full(list, g_free)
5494  * when no longer needed.
5495  *
5496  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5497  *
5498  * Since: 3.26
5499  **/
5500 gboolean
e_book_cache_search_uids(EBookCache * book_cache,const gchar * sexp,GSList ** out_list,GCancellable * cancellable,GError ** error)5501 e_book_cache_search_uids (EBookCache *book_cache,
5502 			  const gchar *sexp,
5503 			  GSList **out_list,
5504 			  GCancellable *cancellable,
5505 			  GError **error)
5506 {
5507 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5508 	g_return_val_if_fail (out_list != NULL, FALSE);
5509 
5510 	*out_list = NULL;
5511 
5512 	return ebc_search_internal (book_cache, sexp, SEARCH_UID, out_list, NULL, NULL, cancellable, error);
5513 }
5514 
5515 /**
5516  * e_book_cache_search_with_callback:
5517  * @book_cache: An #EBookCache
5518  * @sexp: (nullable): search expression; use %NULL or an empty string to get all stored contacts
5519  * @func: (scope call): an #EBookCacheSearchFunc callback to call for each found row
5520  * @user_data: (closure func): user data for @func
5521  * @cancellable: optional #GCancellable object, or %NULL
5522  * @error: return location for a #GError, or %NULL
5523  *
5524  * Similar to e_book_cache_search(), but calls the @func for each found contact.
5525  *
5526  * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
5527  *
5528  * Since: 3.26
5529  **/
5530 gboolean
e_book_cache_search_with_callback(EBookCache * book_cache,const gchar * sexp,EBookCacheSearchFunc func,gpointer user_data,GCancellable * cancellable,GError ** error)5531 e_book_cache_search_with_callback (EBookCache *book_cache,
5532 				   const gchar *sexp,
5533 				   EBookCacheSearchFunc func,
5534 				   gpointer user_data,
5535 				   GCancellable *cancellable,
5536 				   GError **error)
5537 {
5538 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
5539 	g_return_val_if_fail (func != NULL, FALSE);
5540 
5541 	return ebc_search_internal (book_cache, sexp, SEARCH_FULL, NULL, func, user_data, cancellable, error);
5542 }
5543 
5544 /**
5545  * e_book_cache_cursor_new:
5546  * @book_cache: An #EBookCache
5547  * @sexp: search expression; use %NULL or an empty string to get all stored contacts
5548  * @sort_fields: (array length=n_sort_fields): An array of #EContactField(s) as sort keys in order of priority
5549  * @sort_types: (array length=n_sort_fields): An array of #EBookCursorSortTypes, one for each field in @sort_fields
5550  * @n_sort_fields: The number of fields to sort results by
5551  * @error: return location for a #GError, or %NULL
5552  *
5553  * Creates a new #EBookCacheCursor.
5554  *
5555  * The cursor should be freed with e_book_cache_cursor_free() when
5556  * no longer needed.
5557  *
5558  * Returns: (transfer full): A newly created #EBookCacheCursor
5559  *
5560  * Since: 3.26
5561  **/
5562 EBookCacheCursor *
e_book_cache_cursor_new(EBookCache * book_cache,const gchar * sexp,const EContactField * sort_fields,const EBookCursorSortType * sort_types,guint n_sort_fields,GError ** error)5563 e_book_cache_cursor_new (EBookCache *book_cache,
5564 			 const gchar *sexp,
5565 			 const EContactField *sort_fields,
5566 			 const EBookCursorSortType *sort_types,
5567 			 guint n_sort_fields,
5568 			 GError **error)
5569 {
5570 	EBookCacheCursor *cursor;
5571 	gint ii;
5572 
5573 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), NULL);
5574 
5575 	/* We don't like '\0' sexps, prefer NULL */
5576 	if (sexp && !*sexp)
5577 		sexp = NULL;
5578 
5579 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_READ);
5580 
5581 	/* Need one sort key ... */
5582 	if (n_sort_fields == 0) {
5583 		g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
5584 			_("At least one sort field must be specified to use a cursor"));
5585 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5586 		return NULL;
5587 	}
5588 
5589 	/* We only support string fields to sort the cursor */
5590 	for (ii = 0; ii < n_sort_fields; ii++) {
5591 		if (e_contact_field_type (sort_fields[ii]) != G_TYPE_STRING) {
5592 			g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY,
5593 				_("Cannot sort by a field that is not a string type"));
5594 
5595 			e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5596 			return NULL;
5597 		}
5598 	}
5599 
5600 	/* Now we need to create the cursor instance before setting up the query
5601 	 * (not really true, but more convenient that way).
5602 	 */
5603 	cursor = ebc_cursor_new (book_cache, sexp, sort_fields, sort_types, n_sort_fields);
5604 
5605 	/* Setup the cursor's query expression which might fail */
5606 	if (!ebc_cursor_setup_query (book_cache, cursor, sexp, error)) {
5607 		ebc_cursor_free (cursor);
5608 		cursor = NULL;
5609 	}
5610 
5611 	e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5612 
5613 	return cursor;
5614 }
5615 
5616 /**
5617  * e_book_cache_cursor_free:
5618  * @book_cache: An #EBookCache
5619  * @cursor: The #EBookCacheCursor to free
5620  *
5621  * Frees the @cursor, previously allocated with e_book_cache_cursor_new().
5622  *
5623  * Since: 3.26
5624  **/
5625 void
e_book_cache_cursor_free(EBookCache * book_cache,EBookCacheCursor * cursor)5626 e_book_cache_cursor_free (EBookCache *book_cache,
5627 			  EBookCacheCursor *cursor)
5628 {
5629 	g_return_if_fail (E_IS_BOOK_CACHE (book_cache));
5630 	g_return_if_fail (cursor != NULL);
5631 
5632 	ebc_cursor_free (cursor);
5633 }
5634 
5635 typedef struct {
5636 	gint uid_index;
5637 	gint object_index;
5638 	gint extra_index;
5639 
5640 	GSList *results;
5641 	gchar *alloc_vcard;
5642 	const gchar *last_vcard;
5643 
5644 	gboolean collect_results;
5645 	gint n_results;
5646 } CursorCollectData;
5647 
5648 static gboolean
ebc_collect_results_for_cursor_cb(ECache * cache,gint ncols,const gchar * column_names[],const gchar * column_values[],gpointer user_data)5649 ebc_collect_results_for_cursor_cb (ECache *cache,
5650 				   gint ncols,
5651 				   const gchar *column_names[],
5652 				   const gchar *column_values[],
5653 				   gpointer user_data)
5654 {
5655 	CursorCollectData *data = user_data;
5656 	const gchar *object = NULL, *extra = NULL;
5657 
5658 	if (data->uid_index == -1 ||
5659 	    data->object_index == -1 ||
5660 	    data->extra_index == -1) {
5661 		gint ii;
5662 
5663 		for (ii = 0; ii < ncols && (data->uid_index == -1 ||
5664 		     data->object_index == -1 ||
5665 		     data->extra_index == -1); ii++) {
5666 			const gchar *cname = column_names[ii];
5667 
5668 			if (!cname)
5669 				continue;
5670 
5671 			if (g_str_has_prefix (cname, "summary."))
5672 				cname += 8;
5673 
5674 			if (data->uid_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_UID) == 0) {
5675 				data->uid_index = ii;
5676 			} else if (data->object_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_OBJECT) == 0) {
5677 				data->object_index = ii;
5678 			} else if (data->extra_index == -1 && g_ascii_strcasecmp (cname, EBC_COLUMN_EXTRA) == 0) {
5679 				data->extra_index = ii;
5680 			}
5681 		}
5682 
5683 		if (data->object_index == -1)
5684 			data->object_index = -2;
5685 
5686 		if (data->extra_index == -1)
5687 			data->extra_index = -2;
5688 	}
5689 
5690 	g_return_val_if_fail (data->uid_index >= 0 && data->uid_index < ncols, FALSE);
5691 
5692 	if (data->object_index != -2) {
5693 		g_return_val_if_fail (data->object_index >= 0 && data->object_index < ncols, FALSE);
5694 		object = column_values[data->object_index];
5695 	}
5696 
5697 	if (data->extra_index != -2) {
5698 		g_return_val_if_fail (data->extra_index >= 0 && data->extra_index < ncols, FALSE);
5699 		extra = column_values[data->extra_index];
5700 	}
5701 
5702 	if (data->collect_results) {
5703 		EBookCacheSearchData *search_data;
5704 
5705 		search_data = e_book_cache_search_data_new (column_values[data->uid_index], object, extra);
5706 
5707 		data->results = g_slist_prepend (data->results, search_data);
5708 
5709 		data->last_vcard = search_data->vcard;
5710 	} else {
5711 		g_free (data->alloc_vcard);
5712 		data->alloc_vcard = g_strdup (object);
5713 
5714 		data->last_vcard = data->alloc_vcard;
5715 	}
5716 
5717 	data->n_results++;
5718 
5719 	return TRUE;
5720 }
5721 
5722 /**
5723  * e_book_cache_cursor_step:
5724  * @book_cache: An #EBookCache
5725  * @cursor: The #EBookCacheCursor to use
5726  * @flags: The #EBookCacheCursorStepFlags for this step
5727  * @origin: The #EBookCacheCursorOrigin from whence to step
5728  * @count: A positive or negative amount of contacts to try and fetch
5729  * @out_results: (out) (nullable) (element-type EBookCacheSearchData) (transfer full):
5730  *   A return location to store the results, or %NULL if %E_BOOK_CACHE_CURSOR_STEP_FETCH is not specified in @flags.
5731  * @cancellable: optional #GCancellable object, or %NULL
5732  * @error: return location for a #GError, or %NULL
5733  *
5734  * Steps @cursor through its sorted query by a maximum of @count contacts
5735  * starting from @origin.
5736  *
5737  * If @count is negative, then the cursor will move through the list in reverse.
5738  *
5739  * If @cursor reaches the beginning or end of the query results, then the
5740  * returned list might not contain the amount of desired contacts, or might
5741  * return no results if the cursor currently points to the last contact.
5742  * Reaching the end of the list is not considered an error condition. Attempts
5743  * to step beyond the end of the list after having reached the end of the list
5744  * will however trigger an %E_CACHE_ERROR_END_OF_LIST error.
5745  *
5746  * If %E_BOOK_CACHE_CURSOR_STEP_FETCH is specified in @flags, a pointer to
5747  * a %NULL #GSList pointer should be provided for the @out_results parameter.
5748  *
5749  * The result list will be stored to @out_results and should be freed
5750  * with g_slist_free_full (results, e_book_cache_search_data_free);
5751  * when no longer needed.
5752  *
5753  * Returns: The number of contacts traversed if successful, otherwise -1 is
5754  *    returned and the @error is set.
5755  *
5756  * Since: 3.26
5757  **/
5758 gint
e_book_cache_cursor_step(EBookCache * book_cache,EBookCacheCursor * cursor,EBookCacheCursorStepFlags flags,EBookCacheCursorOrigin origin,gint count,GSList ** out_results,GCancellable * cancellable,GError ** error)5759 e_book_cache_cursor_step (EBookCache *book_cache,
5760 			  EBookCacheCursor *cursor,
5761 			  EBookCacheCursorStepFlags flags,
5762 			  EBookCacheCursorOrigin origin,
5763 			  gint count,
5764 			  GSList **out_results,
5765 			  GCancellable *cancellable,
5766 			  GError **error)
5767 {
5768 	CursorCollectData data = { -1, -1, -1, NULL, NULL, NULL, FALSE, 0 };
5769 	CursorState *state;
5770 	GString *query;
5771 	gboolean success;
5772 	EBookCacheCursorOrigin try_position;
5773 
5774 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), -1);
5775 	g_return_val_if_fail (cursor != NULL, -1);
5776 	g_return_val_if_fail ((flags & E_BOOK_CACHE_CURSOR_STEP_FETCH) == 0 ||
5777 			      (out_results != NULL), -1);
5778 
5779 	if (out_results)
5780 		*out_results = NULL;
5781 
5782 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_READ);
5783 
5784 	if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
5785 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5786 		return -1;
5787 	}
5788 
5789 	/* Check if this step should result in an end of list error first */
5790 	try_position = cursor->state.position;
5791 	if (origin != E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT)
5792 		try_position = origin;
5793 
5794 	/* Report errors for requests to run off the end of the list */
5795 	if (try_position == E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN && count < 0) {
5796 		g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_END_OF_LIST,
5797 			_("Tried to step a cursor in reverse, "
5798 			"but cursor is already at the beginning of the contact list"));
5799 
5800 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5801 		return -1;
5802 	} else if (try_position == E_BOOK_CACHE_CURSOR_ORIGIN_END && count > 0) {
5803 		g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_END_OF_LIST,
5804 			_("Tried to step a cursor forwards, "
5805 			"but cursor is already at the end of the contact list"));
5806 
5807 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5808 		return -1;
5809 	}
5810 
5811 	/* Nothing to do, silently return */
5812 	if (count == 0 && try_position == E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT) {
5813 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5814 		return 0;
5815 	}
5816 
5817 	/* If we're not going to modify the position, just use
5818 	 * a copy of the current cursor state.
5819 	 */
5820 	if ((flags & E_BOOK_CACHE_CURSOR_STEP_MOVE) != 0)
5821 		state = &(cursor->state);
5822 	else
5823 		state = cursor_state_copy (cursor, &(cursor->state));
5824 
5825 	/* Every query starts with the STATE_CURRENT position, first
5826 	 * fix up the cursor state according to 'origin'
5827 	 */
5828 	switch (origin) {
5829 	case E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT:
5830 		/* Do nothing, normal operation */
5831 		break;
5832 
5833 	case E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN:
5834 	case E_BOOK_CACHE_CURSOR_ORIGIN_END:
5835 
5836 		/* Prepare the state before executing the query */
5837 		cursor_state_clear (cursor, state, origin);
5838 		break;
5839 	}
5840 
5841 	/* If count is 0 then there is no need to run any
5842 	 * query, however it can be useful if you just want
5843 	 * to move the cursor to the beginning or ending of
5844 	 * the list.
5845 	 */
5846 	if (count == 0) {
5847 		/* Free the state copy if need be */
5848 		if ((flags & E_BOOK_CACHE_CURSOR_STEP_MOVE) == 0)
5849 			cursor_state_free (cursor, state);
5850 
5851 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5852 		return 0;
5853 	}
5854 
5855 	query = g_string_new (cursor->select_vcards);
5856 
5857 	/* Add the filter constraints (if any) */
5858 	if (cursor->query) {
5859 		g_string_append (query, " WHERE ");
5860 
5861 		g_string_append_c (query, '(');
5862 		g_string_append (query, cursor->query);
5863 		g_string_append_c (query, ')');
5864 	}
5865 
5866 	/* Add the cursor constraints (if any) */
5867 	if (state->values[0] != NULL) {
5868 		gchar *constraints = NULL;
5869 
5870 		if (!cursor->query)
5871 			g_string_append (query, " WHERE ");
5872 		else
5873 			g_string_append (query, " AND ");
5874 
5875 		constraints = ebc_cursor_constraints (book_cache, cursor, state, count < 0, FALSE);
5876 
5877 		g_string_append_c (query, '(');
5878 		g_string_append (query, constraints);
5879 		g_string_append_c (query, ')');
5880 
5881 		g_free (constraints);
5882 	}
5883 
5884 	/* Add the sort order */
5885 	g_string_append_c (query, ' ');
5886 	if (count > 0)
5887 		g_string_append (query, cursor->order);
5888 	else
5889 		g_string_append (query, cursor->reverse_order);
5890 
5891 	/* Add the limit */
5892 	g_string_append_printf (query, " LIMIT %d", ABS (count));
5893 
5894 	/* Specify whether we really want results or not */
5895 	data.collect_results = (flags & E_BOOK_CACHE_CURSOR_STEP_FETCH) != 0;
5896 
5897 	/* Execute the query */
5898 	success = e_cache_sqlite_select (E_CACHE (book_cache), query->str,
5899 		ebc_collect_results_for_cursor_cb, &data,
5900 		cancellable, error);
5901 
5902 	/* Lock was obtained above */
5903 	e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
5904 
5905 	g_string_free (query, TRUE);
5906 
5907 	/* If there was no error, update the internal cursor state */
5908 	if (success) {
5909 		if (data.n_results < ABS (count)) {
5910 			/* We've reached the end, clear the current state */
5911 			if (count < 0)
5912 				cursor_state_clear (cursor, state, E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN);
5913 			else
5914 				cursor_state_clear (cursor, state, E_BOOK_CACHE_CURSOR_ORIGIN_END);
5915 
5916 		} else if (data.last_vcard) {
5917 			/* Set the cursor state to the last result */
5918 			cursor_state_set_from_vcard (book_cache, cursor, state, data.last_vcard);
5919 		} else {
5920 			/* Should never get here */
5921 			g_warn_if_reached ();
5922 		}
5923 
5924 		/* Assign the results to return (if any) */
5925 		if (out_results) {
5926 			/* Correct the order of results at the last minute */
5927 			*out_results = g_slist_reverse (data.results);
5928 			data.results = NULL;
5929 		}
5930 	}
5931 
5932 	/* Cleanup what was allocated by collect_results_for_cursor_cb() */
5933 	if (data.results)
5934 		g_slist_free_full (data.results, e_book_cache_search_data_free);
5935 	g_free (data.alloc_vcard);
5936 
5937 	/* Free the copy state if we were working with a copy */
5938 	if ((flags & E_BOOK_CACHE_CURSOR_STEP_MOVE) == 0)
5939 		cursor_state_free (cursor, state);
5940 
5941 	if (success)
5942 		return data.n_results;
5943 
5944 	return -1;
5945 }
5946 
5947 /**
5948  * e_book_cache_cursor_set_target_alphabetic_index:
5949  * @book_cache: An #EBookCache
5950  * @cursor: The #EBookCacheCursor to modify
5951  * @idx: The alphabetic index
5952  *
5953  * Sets the @cursor position to an
5954  * <link linkend="cursor-alphabet">Alphabetic Index</link>
5955  * into the alphabet active in @book_cache's locale.
5956  *
5957  * After setting the target to an alphabetic index, for example the
5958  * index for letter 'E', then further calls to e_book_cache_cursor_step()
5959  * will return results starting with the letter 'E' (or results starting
5960  * with the last result in 'D', if moving in a negative direction).
5961  *
5962  * The passed index must be a valid index in the active locale, knowledge
5963  * on the currently active alphabet index must be obtained using #ECollator
5964  * APIs.
5965  *
5966  * Use e_book_cache_ref_collator() to obtain the active collator for @book_cache.
5967  *
5968  * Since: 3.26
5969  **/
5970 void
e_book_cache_cursor_set_target_alphabetic_index(EBookCache * book_cache,EBookCacheCursor * cursor,gint idx)5971 e_book_cache_cursor_set_target_alphabetic_index (EBookCache *book_cache,
5972 						 EBookCacheCursor *cursor,
5973 						 gint idx)
5974 {
5975 	gint n_labels = 0;
5976 
5977 	g_return_if_fail (E_IS_BOOK_CACHE (book_cache));
5978 	g_return_if_fail (cursor != NULL);
5979 	g_return_if_fail (idx >= 0);
5980 
5981 	e_collator_get_index_labels (book_cache->priv->collator, &n_labels, NULL, NULL, NULL);
5982 	g_return_if_fail (idx < n_labels);
5983 
5984 	cursor_state_clear (cursor, &(cursor->state), E_BOOK_CACHE_CURSOR_ORIGIN_CURRENT);
5985 	if (cursor->n_sort_fields > 0) {
5986 		SummaryField *field;
5987 		gchar *index_key;
5988 
5989 		index_key = e_collator_generate_key_for_index (book_cache->priv->collator, idx);
5990 		field = summary_field_get (book_cache, cursor->sort_fields[0]);
5991 
5992 		if (field && (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
5993 			cursor->state.values[0] = index_key;
5994 		} else {
5995 			cursor->state.values[0] = ebc_encode_vcard_sort_key (index_key);
5996 			g_free (index_key);
5997 		}
5998 	}
5999 }
6000 
6001 /**
6002  * e_book_cache_cursor_set_sexp:
6003  * @book_cache: An #EBookCache
6004  * @cursor: The #EBookCacheCursor to modify
6005  * @sexp: The new query expression for @cursor
6006  * @error: return location for a #GError, or %NULL
6007  *
6008  * Modifies the current query expression for @cursor. This will not
6009  * modify @cursor's state, but will change the outcome of any further
6010  * calls to e_book_cache_cursor_step() or e_book_cache_cursor_calculate().
6011  *
6012  * Returns: %TRUE if the expression was valid and accepted by @cursor
6013  *
6014  * Since: 3.26
6015  **/
6016 gboolean
e_book_cache_cursor_set_sexp(EBookCache * book_cache,EBookCacheCursor * cursor,const gchar * sexp,GError ** error)6017 e_book_cache_cursor_set_sexp (EBookCache *book_cache,
6018 			      EBookCacheCursor *cursor,
6019 			      const gchar *sexp,
6020 			      GError **error)
6021 {
6022 	gboolean success;
6023 
6024 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
6025 	g_return_val_if_fail (cursor != NULL, FALSE);
6026 
6027 	/* We don't like '\0' sexps, prefer NULL */
6028 	if (sexp && !*sexp)
6029 		sexp = NULL;
6030 
6031 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_READ);
6032 
6033 	success = ebc_cursor_setup_query (book_cache, cursor, sexp, error);
6034 
6035 	e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
6036 
6037 	return success;
6038 }
6039 
6040 /**
6041  * e_book_cache_cursor_calculate:
6042  * @book_cache: An #EBookCache
6043  * @cursor: The #EBookCacheCursor
6044  * @out_total: (out) (nullable): A return location to store the total result set for this cursor
6045  * @out_position: (out) (nullable): A return location to store the cursor position
6046  * @cancellable: optional #GCancellable object, or %NULL
6047  * @error: return location for a #GError, or %NULL
6048  *
6049  * Calculates the @out_total amount of results for the @cursor's query expression,
6050  * as well as the current @out_position of @cursor in the results. The @out_position is
6051  * represented as the amount of results which lead up to the current value
6052  * of @cursor, if @cursor currently points to an exact contact, the position
6053  * also includes the cursor contact.
6054  *
6055  * Returns: Whether @out_total and @out_position were successfully calculated.
6056  *
6057  * Since: 3.26
6058  **/
6059 gboolean
e_book_cache_cursor_calculate(EBookCache * book_cache,EBookCacheCursor * cursor,gint * out_total,gint * out_position,GCancellable * cancellable,GError ** error)6060 e_book_cache_cursor_calculate (EBookCache *book_cache,
6061 			       EBookCacheCursor *cursor,
6062 			       gint *out_total,
6063 			       gint *out_position,
6064 			       GCancellable *cancellable,
6065 			       GError **error)
6066 {
6067 	gboolean success = TRUE;
6068 	gint local_total = 0;
6069 
6070 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
6071 	g_return_val_if_fail (cursor != NULL, FALSE);
6072 
6073 	/* If we're in a clear cursor state, then the position is 0 */
6074 	if (out_position && cursor->state.values[0] == NULL) {
6075 		if (cursor->state.position == E_BOOK_CACHE_CURSOR_ORIGIN_BEGIN) {
6076 			/* Mark the local pointer NULL, no need to calculate this anymore */
6077 			*out_position = 0;
6078 			out_position = NULL;
6079 		} else if (cursor->state.position == E_BOOK_CACHE_CURSOR_ORIGIN_END) {
6080 			/* Make sure that we look up the total so we can
6081 			 * set the position to 'total + 1'
6082 			 */
6083 			if (!out_total)
6084 				out_total = &local_total;
6085 		}
6086 	}
6087 
6088 	/* Early return if there is nothing to do */
6089 	if (!out_total && !out_position)
6090 		return TRUE;
6091 
6092 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_READ);
6093 
6094 	if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
6095 		e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
6096 		return FALSE;
6097 	}
6098 
6099 	if (out_total)
6100 		success = cursor_count_total_locked (book_cache, cursor, out_total, cancellable, error);
6101 
6102 	if (success && out_position)
6103 		success = cursor_count_position_locked (book_cache, cursor, out_position, cancellable, error);
6104 
6105 	e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_NONE);
6106 
6107 	/* In the case we're at the end, we just set the position
6108 	 * to be the total + 1
6109 	 */
6110 	if (success && out_position && out_total &&
6111 	    cursor->state.position == E_BOOK_CACHE_CURSOR_ORIGIN_END)
6112 		*out_position = *out_total + 1;
6113 
6114 	return success;
6115 }
6116 
6117 /**
6118  * e_book_cache_cursor_compare_contact:
6119  * @book_cache: An #EBookCache
6120  * @cursor: The #EBookCacheCursor
6121  * @contact: The #EContact to compare
6122  * @out_matches_sexp: (out) (nullable): Whether the contact matches the cursor's search expression
6123  *
6124  * Compares @contact with @cursor and returns whether @contact is less than, equal to, or greater
6125  * than @cursor.
6126  *
6127  * Returns: A value that is less than, equal to, or greater than zero if @contact is found,
6128  *    respectively, to be less than, to match, or be greater than the current value of @cursor.
6129  *
6130  * Since: 3.26
6131  **/
6132 gint
e_book_cache_cursor_compare_contact(EBookCache * book_cache,EBookCacheCursor * cursor,EContact * contact,gboolean * out_matches_sexp)6133 e_book_cache_cursor_compare_contact (EBookCache *book_cache,
6134 				     EBookCacheCursor *cursor,
6135 				     EContact *contact,
6136 				     gboolean *out_matches_sexp)
6137 {
6138 	gint ii;
6139 	gint comparison = 0;
6140 
6141 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), -1);
6142 	g_return_val_if_fail (cursor != NULL, -1);
6143 	g_return_val_if_fail (E_IS_CONTACT (contact), -1);
6144 
6145 	if (out_matches_sexp) {
6146 		if (!cursor->sexp)
6147 			*out_matches_sexp = TRUE;
6148 		else
6149 			*out_matches_sexp = e_book_backend_sexp_match_contact (cursor->sexp, contact);
6150 	}
6151 
6152 	for (ii = 0; ii < cursor->n_sort_fields && comparison == 0; ii++) {
6153 		SummaryField *field;
6154 		gchar *contact_key = NULL;
6155 		const gchar *cursor_key = NULL;
6156 		const gchar *field_value;
6157 		gchar *freeme = NULL;
6158 
6159 		field_value = e_contact_get_const (contact, cursor->sort_fields[ii]);
6160 		if (field_value)
6161 			contact_key = e_collator_generate_key (book_cache->priv->collator, field_value, NULL);
6162 
6163 		field = summary_field_get (book_cache, cursor->sort_fields[ii]);
6164 
6165 		if (field && (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
6166 			cursor_key = cursor->state.values[ii];
6167 		} else {
6168 
6169 			if (cursor->state.values[ii])
6170 				freeme = ebc_decode_vcard_sort_key (cursor->state.values[ii]);
6171 
6172 			cursor_key = freeme;
6173 		}
6174 
6175 		/* Empty state sorts below any contact value, which means the contact sorts above cursor */
6176 		if (cursor_key == NULL)
6177 			comparison = 1;
6178 		else
6179 			/* Check if contact sorts below, equal to, or above the cursor */
6180 			comparison = g_strcmp0 (contact_key, cursor_key);
6181 
6182 		g_free (contact_key);
6183 		g_free (freeme);
6184 	}
6185 
6186 	/* UID tie-breaker */
6187 	if (comparison == 0) {
6188 		const gchar *uid;
6189 
6190 		uid = e_contact_get_const (contact, E_CONTACT_UID);
6191 
6192 		if (cursor->state.last_uid == NULL)
6193 			comparison = 1;
6194 		else if (uid == NULL)
6195 			comparison = -1;
6196 		else
6197 			comparison = strcmp (uid, cursor->state.last_uid);
6198 	}
6199 
6200 	return comparison;
6201 }
6202 
6203 static gchar *
ebc_dup_contact_revision(EBookCache * book_cache,EContact * contact)6204 ebc_dup_contact_revision (EBookCache *book_cache,
6205 			  EContact *contact)
6206 {
6207 	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
6208 
6209 	return e_contact_get (contact, E_CONTACT_REV);
6210 }
6211 
6212 static gboolean
e_book_cache_put_locked(ECache * cache,const gchar * uid,const gchar * revision,const gchar * object,ECacheColumnValues * other_columns,EOfflineState offline_state,gboolean is_replace,GCancellable * cancellable,GError ** error)6213 e_book_cache_put_locked (ECache *cache,
6214 			 const gchar *uid,
6215 			 const gchar *revision,
6216 			 const gchar *object,
6217 			 ECacheColumnValues *other_columns,
6218 			 EOfflineState offline_state,
6219 			 gboolean is_replace,
6220 			 GCancellable *cancellable,
6221 			 GError **error)
6222 {
6223 	EBookCache *book_cache;
6224 	EContact *contact;
6225 	gchar *updated_vcard = NULL;
6226 	gboolean e164_changed;
6227 	gboolean success;
6228 
6229 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
6230 	g_return_val_if_fail (E_CACHE_CLASS (e_book_cache_parent_class)->put_locked != NULL, FALSE);
6231 
6232 	book_cache = E_BOOK_CACHE (cache);
6233 
6234 	contact = e_contact_new_from_vcard_with_uid (object, uid);
6235 
6236 	/* Update E.164 parameters in vcard if needed */
6237 	e164_changed = update_e164_attribute_params (book_cache, contact, book_cache->priv->region_code);
6238 
6239 	if (e164_changed) {
6240 		updated_vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
6241 		object = updated_vcard;
6242 	}
6243 
6244 	success = E_CACHE_CLASS (e_book_cache_parent_class)->put_locked (cache, uid, revision, object, other_columns, offline_state,
6245 		is_replace, cancellable, error);
6246 
6247 	success = success && ebc_update_aux_tables (cache, uid, revision, object, cancellable, error);
6248 
6249 	if (success && e164_changed)
6250 		g_signal_emit (book_cache, signals[E164_CHANGED], 0, contact, is_replace);
6251 
6252 	g_clear_object (&contact);
6253 	g_free (updated_vcard);
6254 
6255 	return success;
6256 }
6257 
6258 static gboolean
e_book_cache_remove_locked(ECache * cache,const gchar * uid,GCancellable * cancellable,GError ** error)6259 e_book_cache_remove_locked (ECache *cache,
6260 			    const gchar *uid,
6261 			    GCancellable *cancellable,
6262 			    GError **error)
6263 {
6264 	gboolean success;
6265 
6266 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
6267 	g_return_val_if_fail (E_CACHE_CLASS (e_book_cache_parent_class)->remove_locked != NULL, FALSE);
6268 
6269 	success = ebc_delete_from_aux_tables (cache, uid, cancellable, error);
6270 
6271 	success = success && E_CACHE_CLASS (e_book_cache_parent_class)->remove_locked (cache, uid, cancellable, error);
6272 
6273 	return success;
6274 }
6275 
6276 static gboolean
e_book_cache_remove_all_locked(ECache * cache,const GSList * uids,GCancellable * cancellable,GError ** error)6277 e_book_cache_remove_all_locked (ECache *cache,
6278 				const GSList *uids,
6279 				GCancellable *cancellable,
6280 				GError **error)
6281 {
6282 	gboolean success;
6283 
6284 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
6285 	g_return_val_if_fail (E_CACHE_CLASS (e_book_cache_parent_class)->remove_all_locked != NULL, FALSE);
6286 
6287 	success = ebc_empty_aux_tables (cache, cancellable, error);
6288 
6289 	success = success && E_CACHE_CLASS (e_book_cache_parent_class)->remove_all_locked (cache, uids, cancellable, error);
6290 
6291 	return success;
6292 }
6293 
6294 static gboolean
e_book_cache_clear_offline_changes_locked(ECache * cache,GCancellable * cancellable,GError ** error)6295 e_book_cache_clear_offline_changes_locked (ECache *cache,
6296 					   GCancellable *cancellable,
6297 					   GError **error)
6298 {
6299 	gboolean success;
6300 
6301 	g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
6302 	g_return_val_if_fail (E_CACHE_CLASS (e_book_cache_parent_class)->clear_offline_changes_locked != NULL, FALSE);
6303 
6304 	/* First check whether there are any locally deleted objects at all */
6305 	if (e_cache_get_count (cache, E_CACHE_INCLUDE_DELETED, cancellable, error) >
6306 	    e_cache_get_count (cache, E_CACHE_EXCLUDE_DELETED, cancellable, error))
6307 		success = ebc_delete_from_aux_tables_offline_deleted (cache, cancellable, error);
6308 	else
6309 		success = TRUE;
6310 
6311 	success = success && E_CACHE_CLASS (e_book_cache_parent_class)->clear_offline_changes_locked (cache, cancellable, error);
6312 
6313 	return success;
6314 }
6315 
6316 static void
e_book_cache_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)6317 e_book_cache_get_property (GObject *object,
6318 			   guint property_id,
6319 			   GValue *value,
6320 			   GParamSpec *pspec)
6321 {
6322 	switch (property_id) {
6323 		case PROP_LOCALE:
6324 			g_value_take_string (
6325 				value,
6326 				e_book_cache_dup_locale (E_BOOK_CACHE (object)));
6327 			return;
6328 	}
6329 
6330 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
6331 }
6332 
6333 static void
e_book_cache_finalize(GObject * object)6334 e_book_cache_finalize (GObject *object)
6335 {
6336 	EBookCache *book_cache = E_BOOK_CACHE (object);
6337 
6338 	g_clear_object (&book_cache->priv->source);
6339 
6340 	g_clear_pointer (&book_cache->priv->collator, e_collator_unref);
6341 
6342 	g_free (book_cache->priv->locale);
6343 	g_free (book_cache->priv->region_code);
6344 
6345 	if (book_cache->priv->summary_fields) {
6346 		summary_fields_array_free (book_cache->priv->summary_fields, book_cache->priv->n_summary_fields);
6347 		book_cache->priv->summary_fields = NULL;
6348 	}
6349 
6350 	/* Chain up to parent's method. */
6351 	G_OBJECT_CLASS (e_book_cache_parent_class)->finalize (object);
6352 }
6353 
6354 static void
e_book_cache_class_init(EBookCacheClass * klass)6355 e_book_cache_class_init (EBookCacheClass *klass)
6356 {
6357 	GObjectClass *object_class;
6358 	ECacheClass *cache_class;
6359 
6360 	object_class = G_OBJECT_CLASS (klass);
6361 	object_class->get_property = e_book_cache_get_property;
6362 	object_class->finalize = e_book_cache_finalize;
6363 
6364 	cache_class = E_CACHE_CLASS (klass);
6365 	cache_class->put_locked = e_book_cache_put_locked;
6366 	cache_class->remove_locked = e_book_cache_remove_locked;
6367 	cache_class->remove_all_locked = e_book_cache_remove_all_locked;
6368 	cache_class->clear_offline_changes_locked = e_book_cache_clear_offline_changes_locked;
6369 
6370 	klass->dup_contact_revision = ebc_dup_contact_revision;
6371 
6372 	g_object_class_install_property (
6373 		object_class,
6374 		PROP_LOCALE,
6375 		g_param_spec_string (
6376 			"locale",
6377 			"Locate",
6378 			"The locale currently being used",
6379 			NULL,
6380 			G_PARAM_READABLE |
6381 			G_PARAM_STATIC_STRINGS));
6382 
6383 	signals[E164_CHANGED] = g_signal_new (
6384 		"e164-changed",
6385 		G_OBJECT_CLASS_TYPE (klass),
6386 		G_SIGNAL_RUN_LAST,
6387 		G_STRUCT_OFFSET (EBookCacheClass, e164_changed),
6388 		NULL,
6389 		NULL,
6390 		g_cclosure_marshal_generic,
6391 		G_TYPE_NONE, 2,
6392 		E_TYPE_CONTACT,
6393 		G_TYPE_BOOLEAN);
6394 
6395 	/**
6396 	 * EBookCache:dup-contact-revision:
6397 	 * A signal being called to get revision of an #EContact.
6398 	 * The default implementation returns E_CONTACT_REV field value.
6399 	 **/
6400 	signals[DUP_CONTACT_REVISION] = g_signal_new (
6401 		"dup-contact-revision",
6402 		G_OBJECT_CLASS_TYPE (klass),
6403 		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
6404 		G_STRUCT_OFFSET (EBookCacheClass, dup_contact_revision),
6405 		g_signal_accumulator_first_wins,
6406 		NULL,
6407 		g_cclosure_marshal_generic,
6408 		G_TYPE_STRING, 1,
6409 		E_TYPE_CONTACT);
6410 }
6411 
6412 static void
e_book_cache_init(EBookCache * book_cache)6413 e_book_cache_init (EBookCache *book_cache)
6414 {
6415 	book_cache->priv = e_book_cache_get_instance_private (book_cache);
6416 }
6417