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