1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com)
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 
18 /**
19  * SECTION: e-book-meta-backend
20  * @include: libedata-book/libedata-book.h
21  * @short_description: An #EBookBackend descendant for book backends
22  *
23  * The #EBookMetaBackend is an abstract #EBookBackend descendant which
24  * aims to implement all evolution-data-server internals for the backend
25  * itself and lefts the backend do as minimum work as possible, like
26  * loading and saving contacts, listing available contacts and so on,
27  * thus the backend implementation can focus on things like converting
28  * (possibly) remote data into vCard objects and back.
29  *
30  * As the #EBookMetaBackend uses an #EBookCache, the offline support
31  * is provided by default.
32  *
33  * The structure is thread safe.
34  **/
35 
36 #include "evolution-data-server-config.h"
37 
38 #include <glib.h>
39 #include <glib/gi18n-lib.h>
40 #include <glib/gstdio.h>
41 
42 #include "e-book-backend-sexp.h"
43 #include "e-book-backend.h"
44 #include "e-data-book-cursor-cache.h"
45 #include "e-data-book-factory.h"
46 
47 #include "e-book-meta-backend.h"
48 
49 #define EBMB_KEY_SYNC_TAG		"ebmb::sync-tag"
50 #define EBMB_KEY_EVER_CONNECTED		"ebmb::ever-connected"
51 #define EBMB_KEY_CONNECTED_WRITABLE	"ebmb::connected-writable"
52 
53 #define LOCAL_PREFIX "file://"
54 
55 /* How many times can repeat an operation when credentials fail. */
56 #define MAX_REPEAT_COUNT 3
57 
58 /* How long to wait for credentials, in seconds, during the operation repeat cycle */
59 #define MAX_WAIT_FOR_CREDENTIALS_SECS 60
60 
61 struct _EBookMetaBackendPrivate {
62 	GMutex connect_lock;
63 	GMutex property_lock;
64 	GMutex wait_credentials_lock;
65 	GCond wait_credentials_cond;
66 	guint wait_credentials_stamp;
67 	GError *create_cache_error;
68 	EBookCache *cache;
69 	ENamedParameters *last_credentials;
70 	GHashTable *view_cancellables;
71 	GCancellable *refresh_cancellable;	/* Set when refreshing the content */
72 	GCancellable *source_changed_cancellable; /* Set when processing source changed signal */
73 	GCancellable *go_offline_cancellable;	/* Set when going offline */
74 	gboolean current_online_state;		/* The only state of the internal structures;
75 						   used to detect false notifications on EBackend::online */
76 	gulong source_changed_id;
77 	gulong notify_online_id;
78 	gulong revision_changed_id;
79 	guint refresh_timeout_id;
80 
81 	gboolean refresh_after_authenticate;
82 	gint ever_connected;
83 	gint connected_writable;
84 	gint64 last_refresh_time;	/* The time when the last refresh was called */
85 
86 	/* Last successful connect data, for some extensions */
87 	guint16 authentication_port;
88 	gchar *authentication_host;
89 	gchar *authentication_user;
90 	gchar *authentication_method;
91 	gchar *authentication_proxy_uid;
92 	gchar *authentication_credential_name;
93 	SoupURI *webdav_soup_uri;
94 
95 	GSList *cursors;
96 };
97 
98 enum {
99 	PROP_0,
100 	PROP_CACHE
101 };
102 
103 enum {
104 	REFRESH_COMPLETED,
105 	SOURCE_CHANGED,
106 	LAST_SIGNAL
107 };
108 
109 static guint signals[LAST_SIGNAL];
110 
111 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (EBookMetaBackend, e_book_meta_backend, E_TYPE_BOOK_BACKEND_SYNC)
112 
113 G_DEFINE_BOXED_TYPE (EBookMetaBackendInfo, e_book_meta_backend_info, e_book_meta_backend_info_copy, e_book_meta_backend_info_free)
114 
115 static void ebmb_schedule_source_changed (EBookMetaBackend *meta_backend);
116 static void ebmb_schedule_go_offline (EBookMetaBackend *meta_backend);
117 static gboolean ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend,
118 						EBookCache *book_cache,
119 						gboolean overwrite_existing,
120 						EConflictResolution conflict_resolution,
121 						/* const */ EContact *in_contact,
122 						const gchar *extra,
123 						guint32 opflags,
124 						const gchar *orig_uid,
125 						gboolean *out_requires_put,
126 						gchar **out_new_uid,
127 						gchar **out_new_extra,
128 						GCancellable *cancellable,
129 						GError **error);
130 
131 /**
132  * e_book_meta_backend_info_new:
133  * @uid: a contact UID; cannot be %NULL
134  * @revision: (nullable): the contact revision; can be %NULL
135  * @object: (nullable): the contact object as a vCard string; can be %NULL
136  * @extra: (nullable): extra backend-specific data; can be %NULL
137  *
138  * Creates a new #EBookMetaBackendInfo prefilled with the given values.
139  *
140  * Returns: (transfer full): A new #EBookMetaBackendInfo. Free it with
141  *    e_book_meta_backend_info_free(), when no longer needed.
142  *
143  * Since: 3.26
144  **/
145 EBookMetaBackendInfo *
e_book_meta_backend_info_new(const gchar * uid,const gchar * revision,const gchar * object,const gchar * extra)146 e_book_meta_backend_info_new (const gchar *uid,
147 			      const gchar *revision,
148 			      const gchar *object,
149 			      const gchar *extra)
150 {
151 	EBookMetaBackendInfo *info;
152 
153 	g_return_val_if_fail (uid != NULL, NULL);
154 
155 	info = g_slice_new0 (EBookMetaBackendInfo);
156 	info->uid = g_strdup (uid);
157 	info->revision = g_strdup (revision);
158 	info->object = g_strdup (object);
159 	info->extra = g_strdup (extra);
160 
161 	return info;
162 }
163 
164 /**
165  * e_book_meta_backend_info_copy:
166  * @src: (nullable): a source EBookMetaBackendInfo to copy, or %NULL
167  *
168  * Returns: (transfer full): Copy of the given @src. Free it with
169  *    e_book_meta_backend_info_free() when no longer needed.
170  *    If the @src is %NULL, then returns %NULL as well.
171  *
172  * Since: 3.26
173  **/
174 EBookMetaBackendInfo *
e_book_meta_backend_info_copy(const EBookMetaBackendInfo * src)175 e_book_meta_backend_info_copy (const EBookMetaBackendInfo *src)
176 {
177 	if (!src)
178 		return NULL;
179 
180 	return e_book_meta_backend_info_new (src->uid, src->revision, src->object, src->extra);
181 }
182 
183 /**
184  * e_book_meta_backend_info_free:
185  * @ptr: (nullable): an #EBookMetaBackendInfo
186  *
187  * Frees the @ptr structure, previously allocated with e_book_meta_backend_info_new()
188  * or e_book_meta_backend_info_copy().
189  *
190  * Since: 3.26
191  **/
192 void
e_book_meta_backend_info_free(gpointer ptr)193 e_book_meta_backend_info_free (gpointer ptr)
194 {
195 	EBookMetaBackendInfo *info = ptr;
196 
197 	if (info) {
198 		g_free (info->uid);
199 		g_free (info->revision);
200 		g_free (info->object);
201 		g_free (info->extra);
202 		g_slice_free (EBookMetaBackendInfo, info);
203 	}
204 }
205 
206 /* Unref returned cancellable with g_object_unref(), when done with it */
207 static GCancellable *
ebmb_create_view_cancellable(EBookMetaBackend * meta_backend,EDataBookView * view)208 ebmb_create_view_cancellable (EBookMetaBackend *meta_backend,
209 			      EDataBookView *view)
210 {
211 	GCancellable *cancellable;
212 
213 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
214 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
215 
216 	g_mutex_lock (&meta_backend->priv->property_lock);
217 
218 	cancellable = g_cancellable_new ();
219 	g_hash_table_insert (meta_backend->priv->view_cancellables, view, g_object_ref (cancellable));
220 
221 	g_mutex_unlock (&meta_backend->priv->property_lock);
222 
223 	return cancellable;
224 }
225 
226 static GCancellable *
ebmb_steal_view_cancellable(EBookMetaBackend * meta_backend,EDataBookView * view)227 ebmb_steal_view_cancellable (EBookMetaBackend *meta_backend,
228 			     EDataBookView *view)
229 {
230 	GCancellable *cancellable;
231 
232 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
233 	g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL);
234 
235 	g_mutex_lock (&meta_backend->priv->property_lock);
236 
237 	cancellable = g_hash_table_lookup (meta_backend->priv->view_cancellables, view);
238 	if (cancellable) {
239 		g_object_ref (cancellable);
240 		g_hash_table_remove (meta_backend->priv->view_cancellables, view);
241 	}
242 
243 	g_mutex_unlock (&meta_backend->priv->property_lock);
244 
245 	return cancellable;
246 }
247 
248 static void
ebmb_update_connection_values(EBookMetaBackend * meta_backend)249 ebmb_update_connection_values (EBookMetaBackend *meta_backend)
250 {
251 	ESource *source;
252 
253 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
254 
255 	source = e_backend_get_source (E_BACKEND (meta_backend));
256 
257 	g_mutex_lock (&meta_backend->priv->property_lock);
258 
259 	meta_backend->priv->authentication_port = 0;
260 	g_clear_pointer (&meta_backend->priv->authentication_host, g_free);
261 	g_clear_pointer (&meta_backend->priv->authentication_user, g_free);
262 	g_clear_pointer (&meta_backend->priv->authentication_method, g_free);
263 	g_clear_pointer (&meta_backend->priv->authentication_proxy_uid, g_free);
264 	g_clear_pointer (&meta_backend->priv->authentication_credential_name, g_free);
265 	g_clear_pointer (&meta_backend->priv->webdav_soup_uri, (GDestroyNotify) soup_uri_free);
266 
267 	if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
268 		ESourceAuthentication *auth_extension;
269 
270 		auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
271 
272 		meta_backend->priv->authentication_port = e_source_authentication_get_port (auth_extension);
273 		meta_backend->priv->authentication_host = e_source_authentication_dup_host (auth_extension);
274 		meta_backend->priv->authentication_user = e_source_authentication_dup_user (auth_extension);
275 		meta_backend->priv->authentication_method = e_source_authentication_dup_method (auth_extension);
276 		meta_backend->priv->authentication_proxy_uid = e_source_authentication_dup_proxy_uid (auth_extension);
277 		meta_backend->priv->authentication_credential_name = e_source_authentication_dup_credential_name (auth_extension);
278 	}
279 
280 	if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
281 		ESourceWebdav *webdav_extension;
282 
283 		webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
284 
285 		meta_backend->priv->webdav_soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
286 	}
287 
288 	g_mutex_unlock (&meta_backend->priv->property_lock);
289 
290 	e_book_meta_backend_set_ever_connected (meta_backend, TRUE);
291 	e_book_meta_backend_set_connected_writable (meta_backend, e_book_backend_get_writable (E_BOOK_BACKEND (meta_backend)));
292 }
293 
294 static gboolean
ebmb_gather_locally_cached_objects_cb(EBookCache * book_cache,const gchar * uid,const gchar * revision,const gchar * object,const gchar * extra,guint32 custom_flags,EOfflineState offline_state,gpointer user_data)295 ebmb_gather_locally_cached_objects_cb (EBookCache *book_cache,
296 				       const gchar *uid,
297 				       const gchar *revision,
298 				       const gchar *object,
299 				       const gchar *extra,
300 				       guint32 custom_flags,
301 				       EOfflineState offline_state,
302 				       gpointer user_data)
303 {
304 	GHashTable *locally_cached = user_data;
305 
306 	g_return_val_if_fail (uid != NULL, FALSE);
307 	g_return_val_if_fail (locally_cached != NULL, FALSE);
308 
309 	if (offline_state == E_OFFLINE_STATE_SYNCED) {
310 		g_hash_table_insert (locally_cached,
311 			g_strdup (uid),
312 			g_strdup (revision));
313 	}
314 
315 	return TRUE;
316 }
317 
318 static gboolean
ebmb_get_changes_sync(EBookMetaBackend * meta_backend,const gchar * last_sync_tag,gboolean is_repeat,gchar ** out_new_sync_tag,gboolean * out_repeat,GSList ** out_created_objects,GSList ** out_modified_objects,GSList ** out_removed_objects,GCancellable * cancellable,GError ** error)319 ebmb_get_changes_sync (EBookMetaBackend *meta_backend,
320 		       const gchar *last_sync_tag,
321 		       gboolean is_repeat,
322 		       gchar **out_new_sync_tag,
323 		       gboolean *out_repeat,
324 		       GSList **out_created_objects,
325 		       GSList **out_modified_objects,
326 		       GSList **out_removed_objects,
327 		       GCancellable *cancellable,
328 		       GError **error)
329 {
330 	EBackend *backend;
331 	GSList *existing_objects = NULL;
332 	gboolean success;
333 
334 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
335 	g_return_val_if_fail (out_created_objects, FALSE);
336 	g_return_val_if_fail (out_modified_objects, FALSE);
337 	g_return_val_if_fail (out_removed_objects, FALSE);
338 
339 	*out_created_objects = NULL;
340 	*out_modified_objects = NULL;
341 	*out_removed_objects = NULL;
342 
343 	backend = E_BACKEND (meta_backend);
344 
345 	if (!e_backend_get_online (backend) &&
346 	    !e_backend_is_destination_reachable (backend, cancellable, NULL))
347 		return TRUE;
348 	else
349 		e_backend_set_online (backend, TRUE);
350 
351 	if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error) ||
352 	    !e_book_meta_backend_list_existing_sync (meta_backend, out_new_sync_tag, &existing_objects, cancellable, error)) {
353 		return FALSE;
354 	}
355 
356 	success = e_book_meta_backend_split_changes_sync (meta_backend, existing_objects, out_created_objects,
357 		out_modified_objects, out_removed_objects, cancellable, error);
358 
359 	g_slist_free_full (existing_objects, e_book_meta_backend_info_free);
360 
361 	return success;
362 }
363 
364 static gboolean
ebmb_search_sync(EBookMetaBackend * meta_backend,const gchar * expr,gboolean meta_contact,GSList ** out_contacts,GCancellable * cancellable,GError ** error)365 ebmb_search_sync (EBookMetaBackend *meta_backend,
366 		  const gchar *expr,
367 		  gboolean meta_contact,
368 		  GSList **out_contacts,
369 		  GCancellable *cancellable,
370 		  GError **error)
371 {
372 	EBookCache *book_cache;
373 	gboolean success;
374 
375 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
376 	g_return_val_if_fail (out_contacts != NULL, FALSE);
377 
378 	*out_contacts = NULL;
379 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
380 
381 	g_return_val_if_fail (book_cache != NULL, FALSE);
382 
383 	success = e_book_cache_search (book_cache, expr, meta_contact, out_contacts, cancellable, error);
384 
385 	if (success) {
386 		GSList *link;
387 
388 		for (link = *out_contacts; link; link = g_slist_next (link)) {
389 			EBookCacheSearchData *search_data = link->data;
390 			EContact *contact = NULL;
391 
392 			if (search_data) {
393 				contact = e_contact_new_from_vcard_with_uid (search_data->vcard, search_data->uid);
394 				e_book_cache_search_data_free (search_data);
395 			}
396 
397 			link->data = contact;
398 		}
399 	}
400 
401 	g_object_unref (book_cache);
402 
403 	return success;
404 }
405 
406 static gboolean
ebmb_search_uids_sync(EBookMetaBackend * meta_backend,const gchar * expr,GSList ** out_uids,GCancellable * cancellable,GError ** error)407 ebmb_search_uids_sync (EBookMetaBackend *meta_backend,
408 		       const gchar *expr,
409 		       GSList **out_uids,
410 		       GCancellable *cancellable,
411 		       GError **error)
412 {
413 	EBookCache *book_cache;
414 	gboolean success;
415 
416 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
417 	g_return_val_if_fail (out_uids != NULL, FALSE);
418 
419 	*out_uids = NULL;
420 
421 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
422 	g_return_val_if_fail (book_cache != NULL, FALSE);
423 
424 	success = e_book_cache_search_uids (book_cache, expr, out_uids, cancellable, error);
425 
426 	g_object_unref (book_cache);
427 
428 	return success;
429 }
430 
431 static gboolean
ebmb_requires_reconnect(EBookMetaBackend * meta_backend)432 ebmb_requires_reconnect (EBookMetaBackend *meta_backend)
433 {
434 	ESource *source;
435 	gboolean requires = FALSE;
436 
437 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
438 
439 	source = e_backend_get_source (E_BACKEND (meta_backend));
440 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
441 
442 	g_mutex_lock (&meta_backend->priv->property_lock);
443 
444 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
445 		ESourceAuthentication *auth_extension;
446 
447 		auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
448 
449 		e_source_extension_property_lock (E_SOURCE_EXTENSION (auth_extension));
450 
451 		requires = meta_backend->priv->authentication_port != e_source_authentication_get_port (auth_extension) ||
452 			g_strcmp0 (meta_backend->priv->authentication_host, e_source_authentication_get_host (auth_extension)) != 0 ||
453 			g_strcmp0 (meta_backend->priv->authentication_user, e_source_authentication_get_user (auth_extension)) != 0 ||
454 			g_strcmp0 (meta_backend->priv->authentication_method, e_source_authentication_get_method (auth_extension)) != 0 ||
455 			g_strcmp0 (meta_backend->priv->authentication_proxy_uid, e_source_authentication_get_proxy_uid (auth_extension)) != 0 ||
456 			g_strcmp0 (meta_backend->priv->authentication_credential_name, e_source_authentication_get_credential_name (auth_extension)) != 0;
457 
458 		e_source_extension_property_unlock (E_SOURCE_EXTENSION (auth_extension));
459 	}
460 
461 	if (!requires && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
462 		ESourceWebdav *webdav_extension;
463 		SoupURI *soup_uri;
464 
465 		webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
466 		soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
467 
468 		requires = (!meta_backend->priv->webdav_soup_uri && soup_uri) ||
469 			(soup_uri && meta_backend->priv->webdav_soup_uri &&
470 			!soup_uri_equal (meta_backend->priv->webdav_soup_uri, soup_uri));
471 
472 		if (soup_uri)
473 			soup_uri_free (soup_uri);
474 	}
475 
476 	g_mutex_unlock (&meta_backend->priv->property_lock);
477 
478 	return requires;
479 }
480 
481 static gboolean
ebmb_get_ssl_error_details(EBookMetaBackend * meta_backend,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors)482 ebmb_get_ssl_error_details (EBookMetaBackend *meta_backend,
483 			    gchar **out_certificate_pem,
484 			    GTlsCertificateFlags *out_certificate_errors)
485 {
486 	return FALSE;
487 }
488 
489 static GSList * /* gchar * */
ebmb_gather_photos_local_filenames(EBookMetaBackend * meta_backend,EContact * contact)490 ebmb_gather_photos_local_filenames (EBookMetaBackend *meta_backend,
491 				    EContact *contact)
492 {
493 	EBookCache *book_cache;
494 	GList *attributes, *link;
495 	GSList *filenames = NULL;
496 	gchar *cache_path;
497 
498 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
499 	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
500 
501 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
502 	g_return_val_if_fail (book_cache != NULL, NULL);
503 
504 	cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache)));
505 
506 	g_object_unref (book_cache);
507 
508 	attributes = e_vcard_get_attributes (E_VCARD (contact));
509 
510 	for (link = attributes; link; link = g_list_next (link)) {
511 		EVCardAttribute *attr = link->data;
512 		const gchar *attr_name;
513 		GList *values;
514 
515 		attr_name = e_vcard_attribute_get_name (attr);
516 		if (!attr_name || (
517 		    g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 &&
518 		    g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) {
519 			continue;
520 		}
521 
522 		values = e_vcard_attribute_get_param (attr, EVC_VALUE);
523 		if (values && g_ascii_strcasecmp (values->data, "uri") == 0) {
524 			gchar *url;
525 
526 			url = e_vcard_attribute_get_value (attr);
527 			if (url && g_str_has_prefix (url, LOCAL_PREFIX)) {
528 				gchar *filename;
529 
530 				filename = g_filename_from_uri (url, NULL, NULL);
531 				if (filename && g_str_has_prefix (filename, cache_path))
532 					filenames = g_slist_prepend (filenames, filename);
533 				else
534 					g_free (filename);
535 			}
536 
537 			g_free (url);
538 		}
539 	}
540 
541 	g_free (cache_path);
542 
543 	return filenames;
544 }
545 
546 static void
ebmb_start_view_thread_func(EBookBackend * book_backend,gpointer user_data,GCancellable * cancellable,GError ** error)547 ebmb_start_view_thread_func (EBookBackend *book_backend,
548 			     gpointer user_data,
549 			     GCancellable *cancellable,
550 			     GError **error)
551 {
552 	EDataBookView *view = user_data;
553 	EBookBackendSExp *sexp;
554 	GSList *contacts = NULL;
555 	const gchar *expr = NULL;
556 	gboolean meta_contact = FALSE;
557 	GHashTable *fields_of_interest;
558 	GError *local_error = NULL;
559 
560 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
561 	g_return_if_fail (E_IS_DATA_BOOK_VIEW (view));
562 
563 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
564 		return;
565 
566 	/* Fill the view with known (locally stored) contacts satisfying the expression */
567 	sexp = e_data_book_view_get_sexp (view);
568 	if (sexp)
569 		expr = e_book_backend_sexp_text (sexp);
570 
571 	fields_of_interest = e_data_book_view_get_fields_of_interest (view);
572 	if (fields_of_interest && g_hash_table_size (fields_of_interest) == 2) {
573 		GHashTableIter iter;
574 		gpointer key, value;
575 
576 		meta_contact = TRUE;
577 
578 		g_hash_table_iter_init (&iter, fields_of_interest);
579 		while (g_hash_table_iter_next (&iter, &key, &value)) {
580 			const gchar *field_name = key;
581 			EContactField field = e_contact_field_id (field_name);
582 
583 			if (field != E_CONTACT_UID &&
584 			    field != E_CONTACT_REV) {
585 				meta_contact = FALSE;
586 				break;
587 			}
588 		}
589 	}
590 
591 	if (e_book_meta_backend_search_sync (E_BOOK_META_BACKEND (book_backend), expr, meta_contact, &contacts, cancellable, &local_error) && contacts) {
592 		if (!g_cancellable_is_cancelled (cancellable)) {
593 			GSList *link;
594 
595 			for (link = contacts; link; link = g_slist_next (link)) {
596 				EContact *contact = link->data;
597 				gchar *vcard;
598 
599 				if (!contact)
600 					continue;
601 
602 				vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
603 				e_data_book_view_notify_update_prefiltered_vcard (view,
604 					e_contact_get_const (contact, E_CONTACT_UID),
605 					vcard);
606 
607 				g_free (vcard);
608 			}
609 		}
610 
611 		g_slist_free_full (contacts, g_object_unref);
612 	}
613 
614 	e_data_book_view_notify_complete (view, local_error);
615 
616 	g_clear_error (&local_error);
617 }
618 
619 static gboolean
ebmb_upload_local_changes_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,EConflictResolution conflict_resolution,GCancellable * cancellable,GError ** error)620 ebmb_upload_local_changes_sync (EBookMetaBackend *meta_backend,
621 				EBookCache *book_cache,
622 				EConflictResolution conflict_resolution,
623 				GCancellable *cancellable,
624 				GError **error)
625 {
626 	GSList *offline_changes, *link;
627 	GHashTable *covered_uids;
628 	ECache *cache;
629 	gboolean success = TRUE;
630 
631 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
632 	g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
633 
634 	cache = E_CACHE (book_cache);
635 	covered_uids = g_hash_table_new (g_str_hash, g_str_equal);
636 
637 	offline_changes = e_cache_get_offline_changes (cache, cancellable, error);
638 	for (link = offline_changes; link && success; link = g_slist_next (link)) {
639 		ECacheOfflineChange *change = link->data;
640 		gchar *extra = NULL;
641 		guint32 opflags = 0;
642 
643 		success = !g_cancellable_set_error_if_cancelled (cancellable, error);
644 		if (!success)
645 			break;
646 
647 		if (!change || g_hash_table_contains (covered_uids, change->uid))
648 			continue;
649 
650 		g_hash_table_insert (covered_uids, change->uid, NULL);
651 
652 		if (!e_book_cache_get_contact_extra (book_cache, change->uid, &extra, cancellable, NULL))
653 			extra = NULL;
654 
655 		if (!e_book_cache_get_contact_custom_flags (book_cache, change->uid, &opflags, cancellable, NULL))
656 			opflags = 0;
657 
658 		if (change->state == E_OFFLINE_STATE_LOCALLY_CREATED ||
659 		    change->state == E_OFFLINE_STATE_LOCALLY_MODIFIED) {
660 			EContact *contact = NULL;
661 
662 			success = e_book_cache_get_contact (book_cache, change->uid, FALSE, &contact, cancellable, error);
663 			if (success) {
664 				success = ebmb_save_contact_wrapper_sync (meta_backend, book_cache,
665 					change->state == E_OFFLINE_STATE_LOCALLY_MODIFIED,
666 					conflict_resolution, contact, extra, opflags, change->uid, NULL, NULL, NULL, cancellable, error);
667 			}
668 
669 			g_clear_object (&contact);
670 		} else if (change->state == E_OFFLINE_STATE_LOCALLY_DELETED) {
671 			GError *local_error = NULL;
672 
673 			success = e_book_meta_backend_remove_contact_sync (meta_backend, conflict_resolution,
674 				change->uid, extra, change->object, opflags, cancellable, &local_error);
675 
676 			if (!success) {
677 				if (g_error_matches (local_error, E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND)) {
678 					g_clear_error (&local_error);
679 					success = TRUE;
680 				} else if (local_error) {
681 					g_propagate_error (error, local_error);
682 				}
683 			}
684 		} else {
685 			g_warn_if_reached ();
686 		}
687 
688 		g_free (extra);
689 	}
690 
691 	g_slist_free_full (offline_changes, e_cache_offline_change_free);
692 	g_hash_table_destroy (covered_uids);
693 
694 	if (success)
695 		success = e_cache_clear_offline_changes (cache, cancellable, error);
696 
697 	return success;
698 }
699 
700 static void
ebmb_foreach_cursor(EBookMetaBackend * meta_backend,EContact * contact,void (* func)(EDataBookCursor * cursor,EContact * contact))701 ebmb_foreach_cursor (EBookMetaBackend *meta_backend,
702 		     EContact *contact,
703 		     void (* func) (EDataBookCursor *cursor, EContact *contact))
704 {
705 	GSList *link;
706 
707 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
708 	g_return_if_fail (func != NULL);
709 
710 	g_mutex_lock (&meta_backend->priv->property_lock);
711 
712 	for (link = meta_backend->priv->cursors; link; link = g_slist_next (link)) {
713 		EDataBookCursor *cursor = link->data;
714 
715 		func (cursor, contact);
716 	}
717 
718 	g_mutex_unlock (&meta_backend->priv->property_lock);
719 }
720 
721 static gboolean
ebmb_maybe_remove_from_cache(EBookMetaBackend * meta_backend,EBookCache * book_cache,ECacheOfflineFlag offline_flag,const gchar * uid,guint32 opflags,GCancellable * cancellable,GError ** error)722 ebmb_maybe_remove_from_cache (EBookMetaBackend *meta_backend,
723 			      EBookCache *book_cache,
724 			      ECacheOfflineFlag offline_flag,
725 			      const gchar *uid,
726 			      guint32 opflags,
727 			      GCancellable *cancellable,
728 			      GError **error)
729 {
730 	EBookBackend *book_backend;
731 	EContact *contact = NULL;
732 	GSList *local_photos, *link;
733 	GError *local_error = NULL;
734 
735 	g_return_val_if_fail (uid != NULL, FALSE);
736 
737 	if (!e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable, &local_error)) {
738 		if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
739 			g_clear_error (&local_error);
740 			return TRUE;
741 		}
742 
743 		g_propagate_error (error, local_error);
744 		return FALSE;
745 	}
746 
747 	book_backend = E_BOOK_BACKEND (meta_backend);
748 
749 	if (!e_book_cache_remove_contact (book_cache, uid, opflags, offline_flag, cancellable, error)) {
750 		g_object_unref (contact);
751 		return FALSE;
752 	}
753 
754 	local_photos = ebmb_gather_photos_local_filenames (meta_backend, contact);
755 	for (link = local_photos; link; link = g_slist_next (link)) {
756 		const gchar *filename = link->data;
757 
758 		if (filename && g_unlink (filename) == -1) {
759 			/* Ignore these errors */
760 		}
761 	}
762 
763 	g_slist_free_full (local_photos, g_free);
764 
765 	e_book_backend_notify_remove (book_backend, uid);
766 
767 	ebmb_foreach_cursor (meta_backend, contact, e_data_book_cursor_contact_removed);
768 
769 	g_object_unref (contact);
770 
771 	return TRUE;
772 }
773 
774 static void ebmb_ensure_refresh_timeout_set_locked (EBookMetaBackend *meta_backend);
775 
776 static gboolean
ebmb_refresh_internal_sync(EBookMetaBackend * meta_backend,gboolean with_connection_error,GCancellable * cancellable,GError ** error)777 ebmb_refresh_internal_sync (EBookMetaBackend *meta_backend,
778 			    gboolean with_connection_error,
779 			    GCancellable *cancellable,
780 			    GError **error)
781 {
782 #ifdef HAVE_GPOWERPROFILEMONITOR
783 	GPowerProfileMonitor *power_monitor;
784 #endif
785 	EBookCache *book_cache;
786 	gboolean success = FALSE, repeat = TRUE, is_repeat = FALSE;
787 
788 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
789 
790 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
791 		goto done;
792 
793 #ifdef HAVE_GPOWERPROFILEMONITOR
794 	/* Silently ignore the refresh request when in the power-saver mode */
795 	power_monitor = g_power_profile_monitor_dup_default ();
796 	if (power_monitor && g_power_profile_monitor_get_power_saver_enabled (power_monitor)) {
797 		g_clear_object (&power_monitor);
798 		success = TRUE;
799 		goto done;
800 	}
801 
802 	g_clear_object (&power_monitor);
803 #endif
804 
805 	e_book_backend_foreach_view_notify_progress (E_BOOK_BACKEND (meta_backend), TRUE, 0, _("Refreshing…"));
806 
807 	if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, with_connection_error ? error : NULL) ||
808 	    !e_backend_get_online (E_BACKEND (meta_backend))) { /* Failed connecting moves backend to offline */
809 		g_mutex_lock (&meta_backend->priv->property_lock);
810 		meta_backend->priv->refresh_after_authenticate = TRUE;
811 		g_mutex_unlock (&meta_backend->priv->property_lock);
812 		goto done;
813 	}
814 
815 	g_mutex_lock (&meta_backend->priv->property_lock);
816 	meta_backend->priv->refresh_after_authenticate = FALSE;
817 	g_mutex_unlock (&meta_backend->priv->property_lock);
818 
819 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
820 	if (!book_cache) {
821 		g_warn_if_reached ();
822 		goto done;
823 	}
824 
825 	if (with_connection_error) {
826 		/* Skip upload when not initiated by the user (as part of the Refresh operation) */
827 		success = TRUE;
828 	} else {
829 		GError *local_error = NULL;
830 
831 		success = ebmb_upload_local_changes_sync (meta_backend, book_cache, E_CONFLICT_RESOLUTION_KEEP_SERVER, cancellable, &local_error);
832 
833 		if (local_error) {
834 			if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
835 				e_backend_set_online (E_BACKEND (meta_backend), FALSE);
836 
837 			g_propagate_error (error, local_error);
838 			success = FALSE;
839 		}
840 	}
841 
842 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_WRITE);
843 
844 	while (repeat && success &&
845 	       !g_cancellable_set_error_if_cancelled (cancellable, error)) {
846 		GSList *created_objects = NULL, *modified_objects = NULL, *removed_objects = NULL;
847 		gchar *last_sync_tag, *new_sync_tag = NULL;
848 		GError *local_error = NULL;
849 
850 		repeat = FALSE;
851 
852 		last_sync_tag = e_cache_dup_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, NULL);
853 		if (last_sync_tag && !*last_sync_tag) {
854 			g_free (last_sync_tag);
855 			last_sync_tag = NULL;
856 		}
857 
858 		success = e_book_meta_backend_get_changes_sync (meta_backend, last_sync_tag, is_repeat, &new_sync_tag, &repeat,
859 			&created_objects, &modified_objects, &removed_objects, cancellable, &local_error);
860 
861 		if (local_error) {
862 			if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
863 				e_backend_set_online (E_BACKEND (meta_backend), FALSE);
864 
865 			g_propagate_error (error, local_error);
866 			local_error = NULL;
867 			success = FALSE;
868 		}
869 
870 		if (success) {
871 			success = e_book_meta_backend_process_changes_sync (meta_backend, created_objects, modified_objects,
872 				removed_objects, cancellable, error);
873 		}
874 
875 		if (success && new_sync_tag)
876 			e_cache_set_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, new_sync_tag, NULL);
877 
878 		g_slist_free_full (created_objects, e_book_meta_backend_info_free);
879 		g_slist_free_full (modified_objects, e_book_meta_backend_info_free);
880 		g_slist_free_full (removed_objects, e_book_meta_backend_info_free);
881 		g_free (last_sync_tag);
882 		g_free (new_sync_tag);
883 
884 		is_repeat = TRUE;
885 	}
886 
887 	/* Always commit, to store at least what could be processed */
888 	e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_COMMIT);
889 
890 	g_object_unref (book_cache);
891 
892  done:
893 	g_mutex_lock (&meta_backend->priv->property_lock);
894 
895 	if (meta_backend->priv->refresh_cancellable == cancellable)
896 		g_clear_object (&meta_backend->priv->refresh_cancellable);
897 
898 	ebmb_ensure_refresh_timeout_set_locked (meta_backend);
899 
900 	meta_backend->priv->last_refresh_time = g_get_real_time ();
901 
902 	g_mutex_unlock (&meta_backend->priv->property_lock);
903 
904 	e_book_backend_foreach_view_notify_progress (E_BOOK_BACKEND (meta_backend), TRUE, 0, NULL);
905 
906 	g_signal_emit (meta_backend, signals[REFRESH_COMPLETED], 0, NULL);
907 
908 	return success;
909 }
910 
911 static void
ebmb_refresh_thread_func(EBookBackend * book_backend,gpointer user_data,GCancellable * cancellable,GError ** error)912 ebmb_refresh_thread_func (EBookBackend *book_backend,
913 			  gpointer user_data,
914 			  GCancellable *cancellable,
915 			  GError **error)
916 {
917 	EBookMetaBackend *meta_backend;
918 
919 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
920 
921 	meta_backend = E_BOOK_META_BACKEND (book_backend);
922 
923 	ebmb_refresh_internal_sync (meta_backend, FALSE, cancellable, error);
924 }
925 
926 static void
ebmb_source_refresh_timeout_cb(ESource * source,gpointer user_data)927 ebmb_source_refresh_timeout_cb (ESource *source,
928 				gpointer user_data)
929 {
930 	GWeakRef *weak_ref = user_data;
931 	EBookMetaBackend *meta_backend;
932 
933 	g_return_if_fail (weak_ref != NULL);
934 
935 	meta_backend = g_weak_ref_get (weak_ref);
936 	if (meta_backend) {
937 		e_book_meta_backend_schedule_refresh (meta_backend);
938 		g_object_unref (meta_backend);
939 	}
940 }
941 
942 /* Should hold the property_lock when calling this */
943 static void
ebmb_ensure_refresh_timeout_set_locked(EBookMetaBackend * meta_backend)944 ebmb_ensure_refresh_timeout_set_locked (EBookMetaBackend *meta_backend)
945 {
946 	if (!meta_backend->priv->refresh_timeout_id) {
947 		ESource *source = e_backend_get_source (E_BACKEND (meta_backend));
948 
949 		meta_backend->priv->refresh_timeout_id = e_source_refresh_add_timeout (source, NULL,
950 			ebmb_source_refresh_timeout_cb, e_weak_ref_new (meta_backend), (GDestroyNotify) e_weak_ref_free);
951 	}
952 }
953 
954 static void
ebmb_source_changed_thread_func(EBookBackend * book_backend,gpointer user_data,GCancellable * cancellable,GError ** error)955 ebmb_source_changed_thread_func (EBookBackend *book_backend,
956 				 gpointer user_data,
957 				 GCancellable *cancellable,
958 				 GError **error)
959 {
960 	EBookMetaBackend *meta_backend;
961 
962 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
963 
964 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
965 		return;
966 
967 	meta_backend = E_BOOK_META_BACKEND (book_backend);
968 
969 	g_mutex_lock (&meta_backend->priv->property_lock);
970 	ebmb_ensure_refresh_timeout_set_locked (meta_backend);
971 	g_mutex_unlock (&meta_backend->priv->property_lock);
972 
973 	g_signal_emit (meta_backend, signals[SOURCE_CHANGED], 0, NULL);
974 
975 	if (e_book_meta_backend_requires_reconnect (meta_backend) &&
976 	    (e_backend_get_online (E_BACKEND (meta_backend)) || e_backend_is_destination_reachable (E_BACKEND (meta_backend), cancellable, NULL))) {
977 		gboolean can_refresh;
978 
979 		g_mutex_lock (&meta_backend->priv->connect_lock);
980 		can_refresh = e_book_meta_backend_disconnect_sync (meta_backend, cancellable, error);
981 		g_mutex_unlock (&meta_backend->priv->connect_lock);
982 
983 		if (can_refresh)
984 			e_book_meta_backend_schedule_refresh (meta_backend);
985 	}
986 
987 	g_mutex_lock (&meta_backend->priv->property_lock);
988 
989 	if (meta_backend->priv->source_changed_cancellable == cancellable)
990 		g_clear_object (&meta_backend->priv->source_changed_cancellable);
991 
992 	g_mutex_unlock (&meta_backend->priv->property_lock);
993 }
994 
995 static void
ebmb_go_offline_thread_func(EBookBackend * book_backend,gpointer user_data,GCancellable * cancellable,GError ** error)996 ebmb_go_offline_thread_func (EBookBackend *book_backend,
997 			     gpointer user_data,
998 			     GCancellable *cancellable,
999 			     GError **error)
1000 {
1001 	EBookMetaBackend *meta_backend;
1002 
1003 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
1004 
1005 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
1006 		return;
1007 
1008 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1009 
1010 	g_mutex_lock (&meta_backend->priv->connect_lock);
1011 	e_book_meta_backend_disconnect_sync (meta_backend, cancellable, error);
1012 	g_mutex_unlock (&meta_backend->priv->connect_lock);
1013 
1014 	g_mutex_lock (&meta_backend->priv->property_lock);
1015 
1016 	if (meta_backend->priv->go_offline_cancellable == cancellable)
1017 		g_clear_object (&meta_backend->priv->go_offline_cancellable);
1018 
1019 	g_mutex_unlock (&meta_backend->priv->property_lock);
1020 }
1021 
1022 static gboolean
ebmb_put_contact(EBookMetaBackend * meta_backend,EBookCache * book_cache,ECacheOfflineFlag offline_flag,EContact * contact,const gchar * extra,guint32 opflags,GCancellable * cancellable,GError ** error)1023 ebmb_put_contact (EBookMetaBackend *meta_backend,
1024 		  EBookCache *book_cache,
1025 		  ECacheOfflineFlag offline_flag,
1026 		  EContact *contact,
1027 		  const gchar *extra,
1028 		  guint32 opflags,
1029 		  GCancellable *cancellable,
1030 		  GError **error)
1031 {
1032 	EContact *existing_contact = NULL;
1033 	gboolean success = TRUE;
1034 
1035 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1036 
1037 	success = e_book_meta_backend_store_inline_photos_sync (meta_backend, contact, cancellable, error);
1038 
1039 	if (success && e_book_cache_get_contact (book_cache,
1040 		e_contact_get_const (contact, E_CONTACT_UID), FALSE, &existing_contact, cancellable, NULL)) {
1041 		GSList *old_photos, *new_photos, *link;
1042 
1043 		old_photos = ebmb_gather_photos_local_filenames (meta_backend, existing_contact);
1044 		if (old_photos) {
1045 			GHashTable *photos_hash;
1046 
1047 			photos_hash = g_hash_table_new (g_str_hash, g_str_equal);
1048 
1049 			new_photos = ebmb_gather_photos_local_filenames (meta_backend, contact);
1050 
1051 			for (link = new_photos; link; link = g_slist_next (link)) {
1052 				const gchar *filename = link->data;
1053 
1054 				if (filename)
1055 					g_hash_table_insert (photos_hash, (gpointer) filename, NULL);
1056 			}
1057 
1058 			for (link = old_photos; link; link = g_slist_next (link)) {
1059 				const gchar *filename = link->data;
1060 
1061 				if (filename && !g_hash_table_contains (photos_hash, filename)) {
1062 					if (g_unlink (filename) == -1) {
1063 						/* Ignore these errors */
1064 					}
1065 				}
1066 			}
1067 
1068 			g_slist_free_full (old_photos, g_free);
1069 			g_slist_free_full (new_photos, g_free);
1070 			g_hash_table_destroy (photos_hash);
1071 		}
1072 	}
1073 
1074 	success = success && e_book_cache_put_contact (book_cache, contact, extra, opflags, offline_flag, cancellable, error);
1075 
1076 	if (success)
1077 		e_book_backend_notify_update (E_BOOK_BACKEND (meta_backend), contact);
1078 
1079 	g_clear_object (&existing_contact);
1080 
1081 	return success;
1082 }
1083 
1084 static gboolean
ebmb_load_contact_wrapper_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,const gchar * uid,const gchar * preloaded_object,const gchar * preloaded_extra,gchar ** out_new_uid,GCancellable * cancellable,GError ** error)1085 ebmb_load_contact_wrapper_sync (EBookMetaBackend *meta_backend,
1086 				EBookCache *book_cache,
1087 				const gchar *uid,
1088 				const gchar *preloaded_object,
1089 				const gchar *preloaded_extra,
1090 				gchar **out_new_uid,
1091 				GCancellable *cancellable,
1092 				GError **error)
1093 {
1094 	ECacheOfflineFlag offline_flag = E_CACHE_IS_ONLINE;
1095 	EContact *contact = NULL;
1096 	gchar *extra = NULL;
1097 	gboolean success = TRUE;
1098 	GError *local_error = NULL;
1099 
1100 	if (preloaded_object && *preloaded_object) {
1101 		contact = e_contact_new_from_vcard_with_uid (preloaded_object, uid);
1102 		if (!contact) {
1103 			g_propagate_error (error, e_client_error_create_fmt (E_CLIENT_ERROR_INVALID_ARG, _("Preloaded object for UID “%s” is invalid"), uid));
1104 			return FALSE;
1105 		}
1106 	} else if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error) ||
1107 		!e_book_meta_backend_load_contact_sync (meta_backend, uid, preloaded_extra, &contact, &extra, cancellable, error)) {
1108 		g_free (extra);
1109 		return FALSE;
1110 	} else if (!contact) {
1111 		g_propagate_error (error, e_client_error_create_fmt (E_CLIENT_ERROR_INVALID_ARG, _("Received object for UID “%s” is invalid"), uid));
1112 		g_free (extra);
1113 		return FALSE;
1114 	}
1115 
1116 	success = ebmb_put_contact (meta_backend, book_cache, offline_flag,
1117 		contact, extra ? extra : preloaded_extra, 0, cancellable, &local_error);
1118 
1119 	if (success && out_new_uid)
1120 		*out_new_uid = e_contact_get (contact, E_CONTACT_UID);
1121 
1122 	g_object_unref (contact);
1123 	g_free (extra);
1124 
1125 	if (local_error) {
1126 		if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
1127 			e_backend_set_online (E_BACKEND (meta_backend), FALSE);
1128 
1129 		g_propagate_error (error, local_error);
1130 		success = FALSE;
1131 	}
1132 
1133 	return success;
1134 }
1135 
1136 static gboolean
ebmb_save_contact_wrapper_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,gboolean overwrite_existing,EConflictResolution conflict_resolution,EContact * in_contact,const gchar * extra,guint32 opflags,const gchar * orig_uid,gboolean * out_requires_put,gchar ** out_new_uid,gchar ** out_new_extra,GCancellable * cancellable,GError ** error)1137 ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend,
1138 				EBookCache *book_cache,
1139 				gboolean overwrite_existing,
1140 				EConflictResolution conflict_resolution,
1141 				/* const */ EContact *in_contact,
1142 				const gchar *extra,
1143 				guint32 opflags,
1144 				const gchar *orig_uid,
1145 				gboolean *out_requires_put,
1146 				gchar **out_new_uid,
1147 				gchar **out_new_extra,
1148 				GCancellable *cancellable,
1149 				GError **error)
1150 {
1151 	EContact *contact;
1152 	gchar *new_uid = NULL, *new_extra = NULL;
1153 	gboolean success = TRUE;
1154 	GError *local_error = NULL;
1155 
1156 	if (out_requires_put)
1157 		*out_requires_put = TRUE;
1158 
1159 	if (out_new_uid)
1160 		*out_new_uid = NULL;
1161 
1162 	contact = e_contact_duplicate (in_contact);
1163 
1164 	success = e_book_meta_backend_inline_local_photos_sync (meta_backend, contact, cancellable, error);
1165 
1166 	success = success && e_book_meta_backend_save_contact_sync (meta_backend, overwrite_existing, conflict_resolution,
1167 		contact, extra, opflags, &new_uid, &new_extra, cancellable, &local_error);
1168 
1169 	if (success && new_uid && *new_uid) {
1170 		gchar *loaded_uid = NULL;
1171 
1172 		success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, new_uid, NULL,
1173 			new_extra ? new_extra : extra, &loaded_uid, cancellable, error);
1174 
1175 		if (success && g_strcmp0 (loaded_uid, orig_uid) != 0)
1176 			success = ebmb_maybe_remove_from_cache (meta_backend, book_cache, E_CACHE_IS_ONLINE, orig_uid, opflags, cancellable, error);
1177 
1178 		if (success && out_new_uid)
1179 			*out_new_uid = loaded_uid;
1180 		else
1181 			g_free (loaded_uid);
1182 
1183 		if (out_requires_put)
1184 			*out_requires_put = FALSE;
1185 	}
1186 
1187 	g_free (new_uid);
1188 
1189 	if (success && out_new_extra)
1190 		*out_new_extra = new_extra;
1191 	else
1192 		g_free (new_extra);
1193 	g_object_unref (contact);
1194 
1195 	if (local_error) {
1196 		if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
1197 			e_backend_set_online (E_BACKEND (meta_backend), FALSE);
1198 
1199 		g_propagate_error (error, local_error);
1200 		success = FALSE;
1201 	}
1202 
1203 	return success;
1204 }
1205 
1206 static gchar *
ebmb_get_backend_property(EBookBackend * book_backend,const gchar * prop_name)1207 ebmb_get_backend_property (EBookBackend *book_backend,
1208 			   const gchar *prop_name)
1209 {
1210 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
1211 	g_return_val_if_fail (prop_name != NULL, NULL);
1212 
1213 	if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REVISION)) {
1214 		EBookCache *book_cache;
1215 		gchar *revision = NULL;
1216 
1217 		book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend));
1218 		if (book_cache) {
1219 			revision = e_cache_dup_revision (E_CACHE (book_cache));
1220 			g_object_unref (book_cache);
1221 		} else {
1222 			g_warn_if_reached ();
1223 		}
1224 
1225 		return revision;
1226 	} else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
1227 		return g_strdup (e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)));
1228 	} else  if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
1229 		return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
1230 	} else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
1231 		GString *fields;
1232 		gint ii;
1233 
1234 		fields = g_string_sized_new (1024);
1235 
1236 		/* Claim to support everything by default */
1237 		for (ii = 1; ii < E_CONTACT_FIELD_LAST; ii++) {
1238 			if (fields->len > 0)
1239 				g_string_append_c (fields, ',');
1240 			g_string_append (fields, e_contact_field_name (ii));
1241 		}
1242 
1243 		return g_string_free (fields, FALSE);
1244 	}
1245 
1246 	/* Chain up to parent's method. */
1247 	return E_BOOK_BACKEND_CLASS (e_book_meta_backend_parent_class)->impl_get_backend_property (book_backend, prop_name);
1248 }
1249 
1250 static gboolean
ebmb_open_sync(EBookBackendSync * book_backend,GCancellable * cancellable,GError ** error)1251 ebmb_open_sync (EBookBackendSync *book_backend,
1252 		GCancellable *cancellable,
1253 		GError **error)
1254 {
1255 	EBookMetaBackend *meta_backend;
1256 	ESource *source;
1257 
1258 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1259 
1260 	if (e_book_backend_is_opened (E_BOOK_BACKEND (book_backend)))
1261 		return TRUE;
1262 
1263 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1264 	if (meta_backend->priv->create_cache_error) {
1265 		g_propagate_error (error, meta_backend->priv->create_cache_error);
1266 		meta_backend->priv->create_cache_error = NULL;
1267 		return FALSE;
1268 	}
1269 
1270 	source = e_backend_get_source (E_BACKEND (book_backend));
1271 
1272 	if (!meta_backend->priv->source_changed_id) {
1273 		meta_backend->priv->source_changed_id = g_signal_connect_swapped (source, "changed",
1274 			G_CALLBACK (ebmb_schedule_source_changed), meta_backend);
1275 	}
1276 
1277 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) {
1278 		ESourceWebdav *webdav_extension;
1279 
1280 		webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1281 		e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
1282 	}
1283 
1284 	if (e_book_meta_backend_get_ever_connected (meta_backend)) {
1285 		e_book_backend_set_writable (E_BOOK_BACKEND (meta_backend),
1286 			e_book_meta_backend_get_connected_writable (meta_backend));
1287 	} else {
1288 		if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error)) {
1289 			g_mutex_lock (&meta_backend->priv->property_lock);
1290 			meta_backend->priv->refresh_after_authenticate = TRUE;
1291 			g_mutex_unlock (&meta_backend->priv->property_lock);
1292 
1293 			return FALSE;
1294 		}
1295 	}
1296 
1297 	e_book_meta_backend_schedule_refresh (E_BOOK_META_BACKEND (book_backend));
1298 
1299 	return TRUE;
1300 }
1301 
1302 static gboolean
ebmb_refresh_sync(EBookBackendSync * book_backend,GCancellable * cancellable,GError ** error)1303 ebmb_refresh_sync (EBookBackendSync *book_backend,
1304 		   GCancellable *cancellable,
1305 		   GError **error)
1306 {
1307 	EBookMetaBackend *meta_backend;
1308 	EBackend *backend;
1309 	gboolean success;
1310 
1311 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1312 
1313 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1314 	backend = E_BACKEND (meta_backend);
1315 
1316 	if (!e_backend_get_online (backend) &&
1317 	    e_backend_is_destination_reachable (backend, cancellable, NULL))
1318 		e_backend_set_online (backend, TRUE);
1319 
1320 	if (!e_backend_get_online (backend))
1321 		return TRUE;
1322 
1323 	success = e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error);
1324 
1325 	if (success)
1326 		e_book_meta_backend_schedule_refresh (meta_backend);
1327 
1328 	return success;
1329 }
1330 
1331 static gboolean
ebmb_create_contact_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,ECacheOfflineFlag * offline_flag,EConflictResolution conflict_resolution,guint32 opflags,EContact * contact,gchar ** out_new_uid,EContact ** out_new_contact,GCancellable * cancellable,GError ** error)1332 ebmb_create_contact_sync (EBookMetaBackend *meta_backend,
1333 			  EBookCache *book_cache,
1334 			  ECacheOfflineFlag *offline_flag,
1335 			  EConflictResolution conflict_resolution,
1336 			  guint32 opflags,
1337 			  EContact *contact,
1338 			  gchar **out_new_uid,
1339 			  EContact **out_new_contact,
1340 			  GCancellable *cancellable,
1341 			  GError **error)
1342 {
1343 	const gchar *uid;
1344 	gchar *new_uid = NULL, *new_extra = NULL;
1345 	gboolean success, requires_put = TRUE;
1346 
1347 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1348 
1349 	uid = e_contact_get_const (contact, E_CONTACT_UID);
1350 	if (!uid) {
1351 		gchar *new_uid;
1352 
1353 		new_uid = e_util_generate_uid ();
1354 		if (!new_uid) {
1355 			g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
1356 			return FALSE;
1357 		}
1358 
1359 		e_contact_set (contact, E_CONTACT_UID, new_uid);
1360 		uid = e_contact_get_const (contact, E_CONTACT_UID);
1361 
1362 		g_free (new_uid);
1363 	}
1364 
1365 	if (e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_EXCLUDE_DELETED)) {
1366 		g_propagate_error (error, e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_ID_ALREADY_EXISTS, NULL));
1367 		return FALSE;
1368 	}
1369 
1370 	if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
1371 		if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) {
1372 			*offline_flag = E_CACHE_IS_ONLINE;
1373 		} else {
1374 			*offline_flag = E_CACHE_IS_OFFLINE;
1375 		}
1376 	}
1377 
1378 	if (*offline_flag == E_CACHE_IS_ONLINE) {
1379 		if (!ebmb_save_contact_wrapper_sync (meta_backend, book_cache, FALSE, conflict_resolution, contact, NULL, opflags, uid,
1380 			&requires_put, &new_uid, &new_extra, cancellable, error)) {
1381 			return FALSE;
1382 		}
1383 	}
1384 
1385 	if (requires_put) {
1386 		success = e_book_cache_put_contact (book_cache, contact, new_extra, opflags, *offline_flag, cancellable, error);
1387 		if (success)
1388 			e_book_backend_notify_update (E_BOOK_BACKEND (meta_backend), contact);
1389 	} else {
1390 		success = TRUE;
1391 	}
1392 
1393 	if (success) {
1394 		if (out_new_uid)
1395 			*out_new_uid = g_strdup (new_uid ? new_uid : uid);
1396 		if (out_new_contact) {
1397 			if (new_uid) {
1398 				if (!e_book_cache_get_contact (book_cache, new_uid, FALSE, out_new_contact, cancellable, NULL))
1399 					*out_new_contact = g_object_ref (contact);
1400 			} else {
1401 				*out_new_contact = g_object_ref (contact);
1402 			}
1403 		}
1404 	}
1405 
1406 	g_free (new_uid);
1407 	g_free (new_extra);
1408 
1409 	return success;
1410 }
1411 
1412 static gboolean
ebmb_create_contacts_sync(EBookBackendSync * book_backend,const gchar * const * vcards,guint32 opflags,GSList ** out_contacts,GCancellable * cancellable,GError ** error)1413 ebmb_create_contacts_sync (EBookBackendSync *book_backend,
1414 			   const gchar * const *vcards,
1415 			   guint32 opflags,
1416 			   GSList **out_contacts,
1417 			   GCancellable *cancellable,
1418 			   GError **error)
1419 {
1420 	EBookMetaBackend *meta_backend;
1421 	EBookCache *book_cache;
1422 	ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
1423 	EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags);
1424 	gint ii;
1425 	gboolean success = TRUE;
1426 
1427 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1428 	g_return_val_if_fail (vcards != NULL, FALSE);
1429 	g_return_val_if_fail (out_contacts != NULL, FALSE);
1430 
1431 	*out_contacts = NULL;
1432 
1433 	if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) {
1434 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL));
1435 		return FALSE;
1436 	}
1437 
1438 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1439 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1440 	g_return_val_if_fail (book_cache != NULL, FALSE);
1441 
1442 	for (ii = 0; vcards[ii] && success; ii++) {
1443 		EContact *contact, *new_contact = NULL;
1444 
1445 		if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
1446 			success = FALSE;
1447 			break;
1448 		}
1449 
1450 		contact = e_contact_new_from_vcard (vcards[ii]);
1451 		if (!contact) {
1452 			g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
1453 			success = FALSE;
1454 			break;
1455 		}
1456 
1457 		success = ebmb_create_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution,
1458 			opflags, contact, NULL, &new_contact, cancellable, error);
1459 
1460 		if (success) {
1461 			ebmb_foreach_cursor (meta_backend, new_contact, e_data_book_cursor_contact_added);
1462 
1463 			*out_contacts = g_slist_prepend (*out_contacts, new_contact);
1464 		}
1465 
1466 		g_object_unref (contact);
1467 	}
1468 
1469 	g_object_unref (book_cache);
1470 
1471 	if (success) {
1472 		*out_contacts = g_slist_reverse (*out_contacts);
1473 	} else {
1474 		g_slist_free_full (*out_contacts, g_object_unref);
1475 		*out_contacts = NULL;
1476 	}
1477 
1478 	return success;
1479 }
1480 
1481 static gboolean
ebmb_modify_contact_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,ECacheOfflineFlag * offline_flag,EConflictResolution conflict_resolution,guint32 opflags,EContact * contact,EContact ** out_new_contact,GCancellable * cancellable,GError ** error)1482 ebmb_modify_contact_sync (EBookMetaBackend *meta_backend,
1483 			  EBookCache *book_cache,
1484 			  ECacheOfflineFlag *offline_flag,
1485 			  EConflictResolution conflict_resolution,
1486 			  guint32 opflags,
1487 			  EContact *contact,
1488 			  EContact **out_new_contact,
1489 			  GCancellable *cancellable,
1490 			  GError **error)
1491 {
1492 	const gchar *uid;
1493 	EContact *existing_contact = NULL;
1494 	gchar *extra = NULL, *new_uid = NULL, *new_extra = NULL;
1495 	gboolean success = TRUE, requires_put = TRUE;
1496 	GError *local_error = NULL;
1497 
1498 	g_return_val_if_fail (contact != NULL, FALSE);
1499 
1500 	uid = e_contact_get_const (contact, E_CONTACT_UID);
1501 	if (!uid) {
1502 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
1503 		return FALSE;
1504 	}
1505 
1506 	if (!e_book_cache_get_contact (book_cache, uid, FALSE, &existing_contact, cancellable, &local_error)) {
1507 		if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
1508 			g_clear_error (&local_error);
1509 			local_error = e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL);
1510 		}
1511 
1512 		g_propagate_error (error, local_error);
1513 
1514 		return FALSE;
1515 	}
1516 
1517 	if (!e_book_cache_get_contact_extra (book_cache, uid, &extra, cancellable, NULL))
1518 		extra = NULL;
1519 
1520 	if (success && *offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
1521 		if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) {
1522 			*offline_flag = E_CACHE_IS_ONLINE;
1523 		} else {
1524 			*offline_flag = E_CACHE_IS_OFFLINE;
1525 		}
1526 	}
1527 
1528 	if (success && *offline_flag == E_CACHE_IS_ONLINE) {
1529 		success = ebmb_save_contact_wrapper_sync (meta_backend, book_cache, TRUE, conflict_resolution,
1530 			contact, extra, opflags, uid, &requires_put, &new_uid, &new_extra, cancellable, error);
1531 	}
1532 
1533 	if (success && requires_put)
1534 		success = ebmb_put_contact (meta_backend, book_cache, *offline_flag, contact, new_extra ? new_extra : extra, opflags, cancellable, error);
1535 
1536 	if (success && out_new_contact) {
1537 		if (new_uid) {
1538 			if (!e_book_cache_get_contact (book_cache, new_uid, FALSE, out_new_contact, cancellable, NULL))
1539 				*out_new_contact = NULL;
1540 		} else {
1541 			*out_new_contact = g_object_ref (contact);
1542 		}
1543 	}
1544 
1545 	g_clear_object (&existing_contact);
1546 	g_free (new_extra);
1547 	g_free (new_uid);
1548 	g_free (extra);
1549 
1550 	return success;
1551 }
1552 
1553 static gboolean
ebmb_modify_contacts_sync(EBookBackendSync * book_backend,const gchar * const * vcards,guint32 opflags,GSList ** out_contacts,GCancellable * cancellable,GError ** error)1554 ebmb_modify_contacts_sync (EBookBackendSync *book_backend,
1555 			   const gchar * const *vcards,
1556 			   guint32 opflags,
1557 			   GSList **out_contacts,
1558 			   GCancellable *cancellable,
1559 			   GError **error)
1560 {
1561 	EBookMetaBackend *meta_backend;
1562 	EBookCache *book_cache;
1563 	ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
1564 	EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags);
1565 	gint ii;
1566 	gboolean success = TRUE;
1567 
1568 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1569 	g_return_val_if_fail (vcards != NULL, FALSE);
1570 	g_return_val_if_fail (out_contacts != NULL, FALSE);
1571 
1572 	*out_contacts = NULL;
1573 
1574 	if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) {
1575 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL));
1576 		return FALSE;
1577 	}
1578 
1579 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1580 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1581 	g_return_val_if_fail (book_cache != NULL, FALSE);
1582 
1583 	for (ii = 0; vcards[ii] && success; ii++) {
1584 		EContact *contact, *new_contact = NULL;
1585 
1586 		if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
1587 			success = FALSE;
1588 			break;
1589 		}
1590 
1591 		contact = e_contact_new_from_vcard (vcards[ii]);
1592 		if (!contact) {
1593 			g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
1594 			success = FALSE;
1595 			break;
1596 		}
1597 
1598 		success = ebmb_modify_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution, opflags,
1599 			contact, &new_contact, cancellable, error);
1600 
1601 		if (success && new_contact) {
1602 			ebmb_foreach_cursor (meta_backend, contact, e_data_book_cursor_contact_removed);
1603 			ebmb_foreach_cursor (meta_backend, new_contact, e_data_book_cursor_contact_added);
1604 
1605 			*out_contacts = g_slist_prepend (*out_contacts, g_object_ref (new_contact));
1606 		}
1607 
1608 		g_clear_object (&new_contact);
1609 		g_object_unref (contact);
1610 	}
1611 
1612 	g_object_unref (book_cache);
1613 
1614 	if (success) {
1615 		*out_contacts = g_slist_reverse (*out_contacts);
1616 	} else {
1617 		g_slist_free_full (*out_contacts, g_object_unref);
1618 		*out_contacts = NULL;
1619 	}
1620 
1621 	return success;
1622 }
1623 
1624 static gboolean
ebmb_remove_contact_sync(EBookMetaBackend * meta_backend,EBookCache * book_cache,ECacheOfflineFlag * offline_flag,EConflictResolution conflict_resolution,guint32 opflags,const gchar * uid,GCancellable * cancellable,GError ** error)1625 ebmb_remove_contact_sync (EBookMetaBackend *meta_backend,
1626 			  EBookCache *book_cache,
1627 			  ECacheOfflineFlag *offline_flag,
1628 			  EConflictResolution conflict_resolution,
1629 			  guint32 opflags,
1630 			  const gchar *uid,
1631 			  GCancellable *cancellable,
1632 			  GError **error)
1633 {
1634 	EContact *existing_contact = NULL;
1635 	gchar *extra = NULL;
1636 	gboolean success = TRUE;
1637 	GError *local_error = NULL;
1638 
1639 	g_return_val_if_fail (uid != NULL, FALSE);
1640 
1641 	if (!e_book_cache_get_contact (book_cache, uid, FALSE, &existing_contact, cancellable, &local_error)) {
1642 		if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
1643 			g_clear_error (&local_error);
1644 			local_error = e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL);
1645 		}
1646 
1647 		g_propagate_error (error, local_error);
1648 
1649 		return FALSE;
1650 	}
1651 
1652 	if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
1653 		if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) {
1654 			*offline_flag = E_CACHE_IS_ONLINE;
1655 		} else {
1656 			*offline_flag = E_CACHE_IS_OFFLINE;
1657 		}
1658 	}
1659 
1660 	if (!e_book_cache_get_contact_extra (book_cache, uid, &extra, cancellable, NULL))
1661 		extra = NULL;
1662 
1663 	if (*offline_flag == E_CACHE_IS_ONLINE &&
1664 	     e_cache_get_offline_state (E_CACHE (book_cache), uid, cancellable, NULL) != E_OFFLINE_STATE_LOCALLY_CREATED) {
1665 		gchar *vcard_string = NULL;
1666 
1667 		g_warn_if_fail (e_book_cache_get_vcard (book_cache, uid, FALSE, &vcard_string, cancellable, NULL));
1668 
1669 		success = e_book_meta_backend_remove_contact_sync (meta_backend, conflict_resolution, uid, extra, vcard_string, opflags, cancellable, &local_error);
1670 
1671 		g_free (vcard_string);
1672 
1673 		if (local_error) {
1674 			if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND))
1675 				e_backend_set_online (E_BACKEND (meta_backend), FALSE);
1676 
1677 			g_propagate_error (error, local_error);
1678 			success = FALSE;
1679 		}
1680 	}
1681 
1682 	success = success && ebmb_maybe_remove_from_cache (meta_backend, book_cache, *offline_flag, uid, opflags, cancellable, error);
1683 
1684 	g_clear_object (&existing_contact);
1685 	g_free (extra);
1686 
1687 	return success;
1688 }
1689 
1690 static gboolean
ebmb_remove_contacts_sync(EBookBackendSync * book_backend,const gchar * const * uids,guint32 opflags,GSList ** out_removed_uids,GCancellable * cancellable,GError ** error)1691 ebmb_remove_contacts_sync (EBookBackendSync *book_backend,
1692 			   const gchar * const *uids,
1693 			   guint32 opflags,
1694 			   GSList **out_removed_uids,
1695 			   GCancellable *cancellable,
1696 			   GError **error)
1697 {
1698 	EBookMetaBackend *meta_backend;
1699 	EBookCache *book_cache;
1700 	ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
1701 	EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags);
1702 	gint ii;
1703 	gboolean success = TRUE;
1704 
1705 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1706 	g_return_val_if_fail (uids != NULL, FALSE);
1707 	g_return_val_if_fail (out_removed_uids != NULL, FALSE);
1708 
1709 	*out_removed_uids = NULL;
1710 
1711 	if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) {
1712 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL));
1713 		return FALSE;
1714 	}
1715 
1716 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1717 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1718 	g_return_val_if_fail (book_cache != NULL, FALSE);
1719 
1720 	for (ii = 0; uids[ii] && success; ii++) {
1721 		const gchar *uid = uids[ii];
1722 
1723 		if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
1724 			success = FALSE;
1725 			break;
1726 		}
1727 
1728 		if (!uid) {
1729 			g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
1730 			success = FALSE;
1731 			break;
1732 		}
1733 
1734 		success = ebmb_remove_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution, opflags, uid, cancellable, error);
1735 		if (success)
1736 			*out_removed_uids = g_slist_prepend (*out_removed_uids, g_strdup (uid));
1737 	}
1738 
1739 	g_object_unref (book_cache);
1740 
1741 	*out_removed_uids = g_slist_reverse (*out_removed_uids);
1742 
1743 	return success;
1744 }
1745 
1746 static EContact *
ebmb_get_contact_sync(EBookBackendSync * book_backend,const gchar * uid,GCancellable * cancellable,GError ** error)1747 ebmb_get_contact_sync (EBookBackendSync *book_backend,
1748 		       const gchar *uid,
1749 		       GCancellable *cancellable,
1750 		       GError **error)
1751 {
1752 	EBookMetaBackend *meta_backend;
1753 	EBookCache *book_cache;
1754 	EContact *contact = NULL;
1755 	GError *local_error = NULL;
1756 
1757 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
1758 	g_return_val_if_fail (uid && *uid, NULL);
1759 
1760 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1761 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1762 
1763 	g_return_val_if_fail (book_cache != NULL, NULL);
1764 
1765 	if (!e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable, &local_error) &&
1766 	    g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
1767 		gchar *loaded_uid = NULL;
1768 		gboolean found = FALSE;
1769 
1770 		g_clear_error (&local_error);
1771 
1772 		/* Ignore errors here, just try whether it's on the remote side, but not in the local cache */
1773 		if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL) &&
1774 		    ebmb_load_contact_wrapper_sync (meta_backend, book_cache, uid, NULL, NULL, &loaded_uid, cancellable, NULL)) {
1775 			found = e_book_cache_get_contact (book_cache, loaded_uid, FALSE, &contact, cancellable, NULL);
1776 		}
1777 
1778 		if (!found)
1779 			g_propagate_error (error, e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL));
1780 
1781 		g_free (loaded_uid);
1782 	} else if (local_error) {
1783 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_OTHER_ERROR, local_error->message));
1784 		g_clear_error (&local_error);
1785 	}
1786 
1787 	g_object_unref (book_cache);
1788 
1789 	return contact;
1790 }
1791 
1792 static gboolean
ebmb_get_contact_list_sync(EBookBackendSync * book_backend,const gchar * query,GSList ** out_contacts,GCancellable * cancellable,GError ** error)1793 ebmb_get_contact_list_sync (EBookBackendSync *book_backend,
1794 			    const gchar *query,
1795 			    GSList **out_contacts,
1796 			    GCancellable *cancellable,
1797 			    GError **error)
1798 {
1799 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1800 	g_return_val_if_fail (out_contacts != NULL, FALSE);
1801 
1802 	*out_contacts = NULL;
1803 
1804 	return e_book_meta_backend_search_sync (E_BOOK_META_BACKEND (book_backend), query, FALSE, out_contacts, cancellable, error);
1805 }
1806 
1807 static gboolean
ebmb_get_contact_list_uids_sync(EBookBackendSync * book_backend,const gchar * query,GSList ** out_uids,GCancellable * cancellable,GError ** error)1808 ebmb_get_contact_list_uids_sync (EBookBackendSync *book_backend,
1809 				 const gchar *query,
1810 				 GSList **out_uids,
1811 				 GCancellable *cancellable,
1812 				 GError **error)
1813 {
1814 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1815 	g_return_val_if_fail (out_uids != NULL, FALSE);
1816 
1817 	*out_uids = NULL;
1818 
1819 	return e_book_meta_backend_search_uids_sync (E_BOOK_META_BACKEND (book_backend), query, out_uids, cancellable, error);
1820 }
1821 
1822 static void
ebmb_start_view(EBookBackend * book_backend,EDataBookView * view)1823 ebmb_start_view (EBookBackend *book_backend,
1824 		 EDataBookView *view)
1825 {
1826 	GCancellable *cancellable;
1827 
1828 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
1829 
1830 	cancellable = ebmb_create_view_cancellable (E_BOOK_META_BACKEND (book_backend), view);
1831 
1832 	e_book_backend_schedule_custom_operation (book_backend, cancellable,
1833 		ebmb_start_view_thread_func, g_object_ref (view), g_object_unref);
1834 
1835 	g_object_unref (cancellable);
1836 }
1837 
1838 static void
ebmb_stop_view(EBookBackend * book_backend,EDataBookView * view)1839 ebmb_stop_view (EBookBackend *book_backend,
1840 		EDataBookView *view)
1841 {
1842 	GCancellable *cancellable;
1843 
1844 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
1845 
1846 	cancellable = ebmb_steal_view_cancellable (E_BOOK_META_BACKEND (book_backend), view);
1847 	if (cancellable) {
1848 		g_cancellable_cancel (cancellable);
1849 		g_object_unref (cancellable);
1850 	}
1851 }
1852 
1853 static EDataBookDirect *
ebmb_get_direct_book(EBookBackend * book_backend)1854 ebmb_get_direct_book (EBookBackend *book_backend)
1855 {
1856 	EBookMetaBackendClass *klass;
1857 	EBookCache *book_cache;
1858 	EDataBookDirect *direct_book;
1859 	const gchar *cache_filename;
1860 	gchar *backend_path;
1861 	gchar *dirname;
1862 	const gchar *modules_env;
1863 
1864 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
1865 
1866 	klass = E_BOOK_META_BACKEND_GET_CLASS (book_backend);
1867 	g_return_val_if_fail (klass != NULL, NULL);
1868 
1869 	if (!klass->backend_module_filename ||
1870 	    !klass->backend_factory_type_name)
1871 		return NULL;
1872 
1873 	book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend));
1874 	g_return_val_if_fail (book_cache != NULL, NULL);
1875 
1876 	cache_filename = e_cache_get_filename (E_CACHE (book_cache));
1877 	dirname = g_path_get_dirname (cache_filename);
1878 
1879 	modules_env = g_getenv (EDS_ADDRESS_BOOK_MODULES);
1880 
1881 	/* Support in-tree testing / relocated modules */
1882 	if (modules_env) {
1883 		backend_path = g_build_filename (modules_env, klass->backend_module_filename, NULL);
1884 	} else if (klass->backend_module_directory) {
1885 		backend_path = g_build_filename (klass->backend_module_directory, klass->backend_module_filename, NULL);
1886 	} else {
1887 		backend_path = g_build_filename (BACKENDDIR, klass->backend_module_filename, NULL);
1888 	}
1889 
1890 	direct_book = e_data_book_direct_new (backend_path, klass->backend_factory_type_name, dirname);
1891 
1892 	g_object_unref (book_cache);
1893 	g_free (backend_path);
1894 	g_free (dirname);
1895 
1896 	return direct_book;
1897 }
1898 
1899 static void
ebmb_configure_direct(EBookBackend * book_backend,const gchar * base_directory)1900 ebmb_configure_direct (EBookBackend *book_backend,
1901 		       const gchar *base_directory)
1902 {
1903 	EBookMetaBackend *meta_backend;
1904 	EBookCache *book_cache;
1905 	const gchar *cache_filename;
1906 	gchar *dirname;
1907 
1908 	g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend));
1909 
1910 	if (!base_directory)
1911 		return;
1912 
1913 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1914 
1915 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1916 	g_return_if_fail (book_cache != NULL);
1917 
1918 	cache_filename = e_cache_get_filename (E_CACHE (book_cache));
1919 	dirname = g_path_get_dirname (cache_filename);
1920 
1921 	/* Did path for the cache change? Change the cache as well */
1922 	if (dirname && !g_str_equal (base_directory, dirname) &&
1923 	    !g_str_has_prefix (dirname, base_directory)) {
1924 		gchar *filename = g_path_get_basename (cache_filename);
1925 		gchar *new_cache_filename;
1926 		EBookCache *new_cache;
1927 		ESource *source;
1928 
1929 		new_cache_filename = g_build_filename (base_directory, filename, NULL);
1930 		source = e_backend_get_source (E_BACKEND (book_backend));
1931 
1932 		g_clear_error (&meta_backend->priv->create_cache_error);
1933 
1934 		new_cache = e_book_cache_new (new_cache_filename, source, NULL, &meta_backend->priv->create_cache_error);
1935 		g_prefix_error (&meta_backend->priv->create_cache_error, _("Failed to create cache “%s”:"), new_cache_filename);
1936 
1937 		if (new_cache) {
1938 			e_book_meta_backend_set_cache (meta_backend, new_cache);
1939 			g_clear_object (&new_cache);
1940 		}
1941 
1942 		g_free (new_cache_filename);
1943 		g_free (filename);
1944 	}
1945 
1946 	g_free (dirname);
1947 	g_object_unref (book_cache);
1948 }
1949 
1950 static gboolean
ebmb_set_locale(EBookBackend * book_backend,const gchar * locale,GCancellable * cancellable,GError ** error)1951 ebmb_set_locale (EBookBackend *book_backend,
1952 		 const gchar *locale,
1953 		 GCancellable *cancellable,
1954 		 GError **error)
1955 {
1956 	EBookMetaBackend *meta_backend;
1957 	EBookCache *book_cache;
1958 	gboolean success;
1959 
1960 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
1961 
1962 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1963 
1964 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1965 	g_return_val_if_fail (book_cache != NULL, FALSE);
1966 
1967 	success = e_book_cache_set_locale (book_cache, locale, cancellable, error);
1968 	if (success) {
1969 		GSList *link;
1970 
1971 		g_mutex_lock (&meta_backend->priv->property_lock);
1972 
1973 		for (link = meta_backend->priv->cursors; success && link; link = g_slist_next (link)) {
1974 			EDataBookCursor *cursor = link->data;
1975 
1976 			success = e_data_book_cursor_load_locale (cursor, NULL, cancellable, error);
1977 		}
1978 
1979 		g_mutex_unlock (&meta_backend->priv->property_lock);
1980 	}
1981 
1982 	g_object_unref (book_cache);
1983 
1984 	return success;
1985 }
1986 
1987 static gchar *
ebmb_dup_locale(EBookBackend * book_backend)1988 ebmb_dup_locale (EBookBackend *book_backend)
1989 {
1990 	EBookMetaBackend *meta_backend;
1991 	EBookCache *book_cache;
1992 	gchar *locale;
1993 
1994 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
1995 
1996 	meta_backend = E_BOOK_META_BACKEND (book_backend);
1997 
1998 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
1999 	g_return_val_if_fail (book_cache != NULL, NULL);
2000 
2001 	locale = e_book_cache_dup_locale (book_cache);
2002 
2003 	g_object_unref (book_cache);
2004 
2005 	return locale;
2006 }
2007 
2008 static EDataBookCursor *
ebmb_create_cursor(EBookBackend * book_backend,EContactField * sort_fields,EBookCursorSortType * sort_types,guint n_fields,GError ** error)2009 ebmb_create_cursor (EBookBackend *book_backend,
2010 		    EContactField *sort_fields,
2011 		    EBookCursorSortType *sort_types,
2012 		    guint n_fields,
2013 		    GError **error)
2014 {
2015 	EBookMetaBackend *meta_backend;
2016 	EBookCache *book_cache;
2017 	EDataBookCursor *cursor;
2018 
2019 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL);
2020 
2021 	meta_backend = E_BOOK_META_BACKEND (book_backend);
2022 
2023 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
2024 	g_return_val_if_fail (book_cache != NULL, NULL);
2025 
2026 	cursor = e_data_book_cursor_cache_new (book_backend, book_cache, sort_fields, sort_types, n_fields, error);
2027 
2028 	if (cursor) {
2029 		g_mutex_lock (&meta_backend->priv->property_lock);
2030 
2031 		meta_backend->priv->cursors = g_slist_prepend (meta_backend->priv->cursors, cursor);
2032 
2033 		g_mutex_unlock (&meta_backend->priv->property_lock);
2034 	}
2035 
2036 	g_object_unref (book_cache);
2037 
2038 	return cursor;
2039 }
2040 
2041 static gboolean
ebmb_delete_cursor(EBookBackend * book_backend,EDataBookCursor * cursor,GError ** error)2042 ebmb_delete_cursor (EBookBackend *book_backend,
2043 		    EDataBookCursor *cursor,
2044 		    GError **error)
2045 {
2046 	EBookMetaBackend *meta_backend;
2047 	GSList *link;
2048 
2049 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE);
2050 
2051 	meta_backend = E_BOOK_META_BACKEND (book_backend);
2052 
2053 	g_mutex_lock (&meta_backend->priv->property_lock);
2054 
2055 	link = g_slist_find (meta_backend->priv->cursors, cursor);
2056 
2057 	if (link) {
2058 		meta_backend->priv->cursors = g_slist_remove (meta_backend->priv->cursors, cursor);
2059 		g_object_unref (cursor);
2060 	} else {
2061 		g_set_error_literal (
2062 			error,
2063 			E_CLIENT_ERROR,
2064 			E_CLIENT_ERROR_INVALID_ARG,
2065 			_("Requested to delete an unrelated cursor"));
2066 	}
2067 
2068 	g_mutex_unlock (&meta_backend->priv->property_lock);
2069 
2070 	return link != NULL;
2071 }
2072 
2073 static ESourceAuthenticationResult
ebmb_authenticate_sync(EBackend * backend,const ENamedParameters * credentials,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)2074 ebmb_authenticate_sync (EBackend *backend,
2075 			const ENamedParameters *credentials,
2076 			gchar **out_certificate_pem,
2077 			GTlsCertificateFlags *out_certificate_errors,
2078 			GCancellable *cancellable,
2079 			GError **error)
2080 {
2081 	EBookMetaBackend *meta_backend;
2082 	ESourceAuthenticationResult auth_result = E_SOURCE_AUTHENTICATION_UNKNOWN;
2083 	gboolean success, refresh_after_authenticate = FALSE;
2084 
2085 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (backend), E_SOURCE_AUTHENTICATION_ERROR);
2086 
2087 	meta_backend = E_BOOK_META_BACKEND (backend);
2088 
2089 	if (!e_backend_get_online (backend) &&
2090 	    !e_backend_is_destination_reachable (backend, cancellable, NULL)) {
2091 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_REPOSITORY_OFFLINE, NULL));
2092 
2093 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
2094 		meta_backend->priv->wait_credentials_stamp++;
2095 		g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
2096 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
2097 
2098 		return E_SOURCE_AUTHENTICATION_ERROR;
2099 	}
2100 
2101 	g_mutex_lock (&meta_backend->priv->connect_lock);
2102 
2103 	/* Always disconnect first, then provide new credentials. */
2104 	e_book_meta_backend_disconnect_sync (meta_backend, cancellable, NULL);
2105 
2106 	e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_CONNECTING);
2107 
2108 	success = e_book_meta_backend_connect_sync (meta_backend, credentials, &auth_result,
2109 		out_certificate_pem, out_certificate_errors, cancellable, error);
2110 
2111 	if (success) {
2112 		ebmb_update_connection_values (meta_backend);
2113 		auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
2114 
2115 		e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_CONNECTED);
2116 	} else {
2117 		if (auth_result == E_SOURCE_AUTHENTICATION_UNKNOWN)
2118 			auth_result = E_SOURCE_AUTHENTICATION_ERROR;
2119 
2120 		e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
2121 	}
2122 	g_mutex_unlock (&meta_backend->priv->connect_lock);
2123 
2124 	g_mutex_lock (&meta_backend->priv->property_lock);
2125 
2126 	e_named_parameters_free (meta_backend->priv->last_credentials);
2127 	if (success) {
2128 		meta_backend->priv->last_credentials = e_named_parameters_new_clone (credentials);
2129 
2130 		refresh_after_authenticate = meta_backend->priv->refresh_after_authenticate;
2131 		meta_backend->priv->refresh_after_authenticate = FALSE;
2132 	} else {
2133 		meta_backend->priv->last_credentials = NULL;
2134 	}
2135 
2136 	g_mutex_unlock (&meta_backend->priv->property_lock);
2137 
2138 	g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
2139 	meta_backend->priv->wait_credentials_stamp++;
2140 	g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
2141 	g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
2142 
2143 	if (refresh_after_authenticate)
2144 		e_book_meta_backend_schedule_refresh (meta_backend);
2145 
2146 	return auth_result;
2147 }
2148 
2149 static void
ebmb_schedule_source_changed(EBookMetaBackend * meta_backend)2150 ebmb_schedule_source_changed (EBookMetaBackend *meta_backend)
2151 {
2152 	GCancellable *cancellable;
2153 
2154 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2155 
2156 	g_mutex_lock (&meta_backend->priv->property_lock);
2157 
2158 	if (meta_backend->priv->source_changed_cancellable) {
2159 		/* Already updating */
2160 		g_mutex_unlock (&meta_backend->priv->property_lock);
2161 		return;
2162 	}
2163 
2164 	cancellable = g_cancellable_new ();
2165 	meta_backend->priv->source_changed_cancellable = g_object_ref (cancellable);
2166 
2167 	g_mutex_unlock (&meta_backend->priv->property_lock);
2168 
2169 	e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable,
2170 		ebmb_source_changed_thread_func, NULL, NULL);
2171 
2172 	g_object_unref (cancellable);
2173 }
2174 
2175 static void
ebmb_schedule_go_offline(EBookMetaBackend * meta_backend)2176 ebmb_schedule_go_offline (EBookMetaBackend *meta_backend)
2177 {
2178 	GCancellable *cancellable;
2179 
2180 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2181 
2182 	g_mutex_lock (&meta_backend->priv->property_lock);
2183 
2184 	/* Cancel anything ongoing now, but disconnect in a dedicated thread */
2185 	if (meta_backend->priv->refresh_cancellable) {
2186 		g_cancellable_cancel (meta_backend->priv->refresh_cancellable);
2187 		g_clear_object (&meta_backend->priv->refresh_cancellable);
2188 	}
2189 
2190 	if (meta_backend->priv->source_changed_cancellable) {
2191 		g_cancellable_cancel (meta_backend->priv->source_changed_cancellable);
2192 		g_clear_object (&meta_backend->priv->source_changed_cancellable);
2193 	}
2194 
2195 	if (meta_backend->priv->go_offline_cancellable) {
2196 		/* Already going offline */
2197 		g_mutex_unlock (&meta_backend->priv->property_lock);
2198 		return;
2199 	}
2200 
2201 	cancellable = g_cancellable_new ();
2202 	meta_backend->priv->go_offline_cancellable = g_object_ref (cancellable);
2203 
2204 	g_mutex_unlock (&meta_backend->priv->property_lock);
2205 
2206 	e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable,
2207 		ebmb_go_offline_thread_func, NULL, NULL);
2208 
2209 	g_object_unref (cancellable);
2210 }
2211 
2212 static void
ebmb_notify_online_cb(GObject * object,GParamSpec * param,gpointer user_data)2213 ebmb_notify_online_cb (GObject *object,
2214 		       GParamSpec *param,
2215 		       gpointer user_data)
2216 {
2217 	EBookMetaBackend *meta_backend = user_data;
2218 	gboolean new_value;
2219 
2220 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2221 
2222 	new_value = e_backend_get_online (E_BACKEND (meta_backend));
2223 	if (!new_value == !meta_backend->priv->current_online_state)
2224 		return;
2225 
2226 	meta_backend->priv->current_online_state = new_value;
2227 
2228 	if (new_value) {
2229 		gint64 now = g_get_real_time ();
2230 
2231 		/* Do not auto-run refresh (due to getting online) more than once per hour */
2232 		if (now - meta_backend->priv->last_refresh_time >= G_USEC_PER_SEC * ((gint64) 60) * 60) {
2233 			meta_backend->priv->last_refresh_time = now;
2234 
2235 			e_book_meta_backend_schedule_refresh (meta_backend);
2236 		}
2237 	} else {
2238 		ebmb_schedule_go_offline (meta_backend);
2239 	}
2240 }
2241 
2242 static void
ebmb_cancel_view_cb(gpointer key,gpointer value,gpointer user_data)2243 ebmb_cancel_view_cb (gpointer key,
2244 		     gpointer value,
2245 		     gpointer user_data)
2246 {
2247 	GCancellable *cancellable = value;
2248 
2249 	g_return_if_fail (G_IS_CANCELLABLE (cancellable));
2250 
2251 	g_cancellable_cancel (cancellable);
2252 }
2253 
2254 static void
ebmb_wait_for_credentials_cancelled_cb(GCancellable * cancellable,gpointer user_data)2255 ebmb_wait_for_credentials_cancelled_cb (GCancellable *cancellable,
2256 					gpointer user_data)
2257 {
2258 	EBookMetaBackend *meta_backend = user_data;
2259 
2260 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2261 
2262 	g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
2263 	g_cond_broadcast (&meta_backend->priv->wait_credentials_cond);
2264 	g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
2265 }
2266 
2267 static gboolean
ebmb_maybe_wait_for_credentials(EBookMetaBackend * meta_backend,guint wait_credentials_stamp,const GError * op_error,GCancellable * cancellable)2268 ebmb_maybe_wait_for_credentials (EBookMetaBackend *meta_backend,
2269 				 guint wait_credentials_stamp,
2270 				 const GError *op_error,
2271 				 GCancellable *cancellable)
2272 {
2273 	EBackend *backend;
2274 	ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN;
2275 	gchar *certificate_pem = NULL;
2276 	GTlsCertificateFlags certificate_errors = 0;
2277 	gboolean got_credentials = FALSE;
2278 	GError *local_error = NULL;
2279 
2280 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
2281 
2282 	if (!op_error || g_cancellable_is_cancelled (cancellable))
2283 		return FALSE;
2284 
2285 	if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_TLS_NOT_AVAILABLE) &&
2286 	    e_book_meta_backend_get_ssl_error_details (meta_backend, &certificate_pem, &certificate_errors)) {
2287 		reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
2288 	} else if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_REQUIRED)) {
2289 		reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
2290 	} else if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) {
2291 		reason = E_SOURCE_CREDENTIALS_REASON_REJECTED;
2292 	}
2293 
2294 	if (reason == E_SOURCE_CREDENTIALS_REASON_UNKNOWN)
2295 		return FALSE;
2296 
2297 	backend = E_BACKEND (meta_backend);
2298 
2299 	g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
2300 
2301 	if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp ||
2302 	    e_backend_credentials_required_sync (backend, reason, certificate_pem, certificate_errors,
2303 		op_error, cancellable, &local_error)) {
2304 		gint64 wait_end_time;
2305 		gulong handler_id;
2306 
2307 		wait_end_time = g_get_monotonic_time () + MAX_WAIT_FOR_CREDENTIALS_SECS * G_TIME_SPAN_SECOND;
2308 
2309 		handler_id = cancellable ? g_signal_connect (cancellable, "cancelled",
2310 			G_CALLBACK (ebmb_wait_for_credentials_cancelled_cb), meta_backend) : 0;
2311 
2312 		while (wait_credentials_stamp == meta_backend->priv->wait_credentials_stamp &&
2313 		       !g_cancellable_is_cancelled (cancellable)) {
2314 			if (!g_cond_wait_until (&meta_backend->priv->wait_credentials_cond, &meta_backend->priv->wait_credentials_lock, wait_end_time))
2315 				break;
2316 		}
2317 
2318 		if (handler_id)
2319 			g_signal_handler_disconnect (cancellable, handler_id);
2320 
2321 		if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp)
2322 			got_credentials = e_source_get_connection_status (e_backend_get_source (backend)) == E_SOURCE_CONNECTION_STATUS_CONNECTED;
2323 	} else {
2324 		g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
2325 	}
2326 
2327 	g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
2328 
2329 	g_clear_error (&local_error);
2330 	g_free (certificate_pem);
2331 
2332 	return got_credentials;
2333 }
2334 
2335 static void
e_book_meta_backend_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)2336 e_book_meta_backend_set_property (GObject *object,
2337 				  guint property_id,
2338 				  const GValue *value,
2339 				  GParamSpec *pspec)
2340 {
2341 	switch (property_id) {
2342 		case PROP_CACHE:
2343 			e_book_meta_backend_set_cache (
2344 				E_BOOK_META_BACKEND (object),
2345 				g_value_get_object (value));
2346 			return;
2347 	}
2348 
2349 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2350 }
2351 
2352 static void
e_book_meta_backend_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)2353 e_book_meta_backend_get_property (GObject *object,
2354 				  guint property_id,
2355 				  GValue *value,
2356 				  GParamSpec *pspec)
2357 {
2358 	switch (property_id) {
2359 		case PROP_CACHE:
2360 			g_value_take_object (
2361 				value,
2362 				e_book_meta_backend_ref_cache (
2363 				E_BOOK_META_BACKEND (object)));
2364 			return;
2365 	}
2366 
2367 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2368 }
2369 
2370 static void
e_book_meta_backend_constructed(GObject * object)2371 e_book_meta_backend_constructed (GObject *object)
2372 {
2373 	EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object);
2374 
2375 	/* Chain up to parent's method. */
2376 	G_OBJECT_CLASS (e_book_meta_backend_parent_class)->constructed (object);
2377 
2378 	meta_backend->priv->current_online_state = e_backend_get_online (E_BACKEND (meta_backend));
2379 
2380 	meta_backend->priv->notify_online_id = g_signal_connect (meta_backend, "notify::online",
2381 		G_CALLBACK (ebmb_notify_online_cb), meta_backend);
2382 
2383 	if (!meta_backend->priv->cache) {
2384 		EBookCache *cache;
2385 		ESource *source;
2386 		gchar *filename;
2387 
2388 		source = e_backend_get_source (E_BACKEND (meta_backend));
2389 		filename = g_build_filename (e_book_backend_get_cache_dir (E_BOOK_BACKEND (meta_backend)), "cache.db", NULL);
2390 		cache = e_book_cache_new (filename, source, NULL, &meta_backend->priv->create_cache_error);
2391 		g_prefix_error (&meta_backend->priv->create_cache_error, _("Failed to create cache “%s”:"), filename);
2392 
2393 		g_free (filename);
2394 
2395 		if (cache) {
2396 			e_book_meta_backend_set_cache (meta_backend, cache);
2397 			g_clear_object (&cache);
2398 		}
2399 	}
2400 }
2401 
2402 static void
e_book_meta_backend_dispose(GObject * object)2403 e_book_meta_backend_dispose (GObject *object)
2404 {
2405 	EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object);
2406 	ESource *source = e_backend_get_source (E_BACKEND (meta_backend));
2407 
2408 	g_mutex_lock (&meta_backend->priv->property_lock);
2409 
2410 	if (meta_backend->priv->cursors) {
2411 		g_slist_free_full (meta_backend->priv->cursors, g_object_unref);
2412 		meta_backend->priv->cursors = NULL;
2413 	}
2414 
2415 	if (meta_backend->priv->refresh_timeout_id) {
2416 		if (source)
2417 			e_source_refresh_remove_timeout (source, meta_backend->priv->refresh_timeout_id);
2418 		meta_backend->priv->refresh_timeout_id = 0;
2419 	}
2420 
2421 	if (meta_backend->priv->source_changed_id) {
2422 		if (source)
2423 			g_signal_handler_disconnect (source, meta_backend->priv->source_changed_id);
2424 		meta_backend->priv->source_changed_id = 0;
2425 	}
2426 
2427 	if (meta_backend->priv->notify_online_id) {
2428 		g_signal_handler_disconnect (meta_backend, meta_backend->priv->notify_online_id);
2429 		meta_backend->priv->notify_online_id = 0;
2430 	}
2431 
2432 	if (meta_backend->priv->revision_changed_id) {
2433 		if (meta_backend->priv->cache)
2434 			g_signal_handler_disconnect (meta_backend->priv->cache, meta_backend->priv->revision_changed_id);
2435 		meta_backend->priv->revision_changed_id = 0;
2436 	}
2437 
2438 	g_hash_table_foreach (meta_backend->priv->view_cancellables, ebmb_cancel_view_cb, NULL);
2439 
2440 	if (meta_backend->priv->refresh_cancellable) {
2441 		g_cancellable_cancel (meta_backend->priv->refresh_cancellable);
2442 		g_clear_object (&meta_backend->priv->refresh_cancellable);
2443 	}
2444 
2445 	if (meta_backend->priv->source_changed_cancellable) {
2446 		g_cancellable_cancel (meta_backend->priv->source_changed_cancellable);
2447 		g_clear_object (&meta_backend->priv->source_changed_cancellable);
2448 	}
2449 
2450 	if (meta_backend->priv->go_offline_cancellable) {
2451 		g_cancellable_cancel (meta_backend->priv->go_offline_cancellable);
2452 		g_clear_object (&meta_backend->priv->go_offline_cancellable);
2453 	}
2454 
2455 	e_named_parameters_free (meta_backend->priv->last_credentials);
2456 	meta_backend->priv->last_credentials = NULL;
2457 
2458 	g_mutex_unlock (&meta_backend->priv->property_lock);
2459 
2460 	/* Chain up to parent's method. */
2461 	G_OBJECT_CLASS (e_book_meta_backend_parent_class)->dispose (object);
2462 }
2463 
2464 static void
e_book_meta_backend_finalize(GObject * object)2465 e_book_meta_backend_finalize (GObject *object)
2466 {
2467 	EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object);
2468 
2469 	g_clear_object (&meta_backend->priv->cache);
2470 	g_clear_object (&meta_backend->priv->refresh_cancellable);
2471 	g_clear_object (&meta_backend->priv->source_changed_cancellable);
2472 	g_clear_object (&meta_backend->priv->go_offline_cancellable);
2473 	g_clear_error (&meta_backend->priv->create_cache_error);
2474 	g_clear_pointer (&meta_backend->priv->authentication_host, g_free);
2475 	g_clear_pointer (&meta_backend->priv->authentication_user, g_free);
2476 	g_clear_pointer (&meta_backend->priv->authentication_method, g_free);
2477 	g_clear_pointer (&meta_backend->priv->authentication_proxy_uid, g_free);
2478 	g_clear_pointer (&meta_backend->priv->authentication_credential_name, g_free);
2479 	g_clear_pointer (&meta_backend->priv->webdav_soup_uri, (GDestroyNotify) soup_uri_free);
2480 
2481 	g_mutex_clear (&meta_backend->priv->connect_lock);
2482 	g_mutex_clear (&meta_backend->priv->property_lock);
2483 	g_mutex_clear (&meta_backend->priv->wait_credentials_lock);
2484 	g_cond_clear (&meta_backend->priv->wait_credentials_cond);
2485 	g_hash_table_destroy (meta_backend->priv->view_cancellables);
2486 
2487 	/* Chain up to parent's method. */
2488 	G_OBJECT_CLASS (e_book_meta_backend_parent_class)->finalize (object);
2489 }
2490 
2491 static void
e_book_meta_backend_class_init(EBookMetaBackendClass * klass)2492 e_book_meta_backend_class_init (EBookMetaBackendClass *klass)
2493 {
2494 	GObjectClass *object_class;
2495 	EBackendClass *backend_class;
2496 	EBookBackendClass *book_backend_class;
2497 	EBookBackendSyncClass *book_backend_sync_class;
2498 
2499 	klass->backend_factory_type_name = NULL;
2500 	klass->backend_module_filename = NULL;
2501 	klass->get_changes_sync = ebmb_get_changes_sync;
2502 	klass->search_sync = ebmb_search_sync;
2503 	klass->search_uids_sync = ebmb_search_uids_sync;
2504 	klass->requires_reconnect = ebmb_requires_reconnect;
2505 	klass->get_ssl_error_details = ebmb_get_ssl_error_details;
2506 
2507 	book_backend_sync_class = E_BOOK_BACKEND_SYNC_CLASS (klass);
2508 	book_backend_sync_class->open_sync = ebmb_open_sync;
2509 	book_backend_sync_class->refresh_sync = ebmb_refresh_sync;
2510 	book_backend_sync_class->create_contacts_sync = ebmb_create_contacts_sync;
2511 	book_backend_sync_class->modify_contacts_sync = ebmb_modify_contacts_sync;
2512 	book_backend_sync_class->remove_contacts_sync = ebmb_remove_contacts_sync;
2513 	book_backend_sync_class->get_contact_sync = ebmb_get_contact_sync;
2514 	book_backend_sync_class->get_contact_list_sync = ebmb_get_contact_list_sync;
2515 	book_backend_sync_class->get_contact_list_uids_sync = ebmb_get_contact_list_uids_sync;
2516 
2517 	book_backend_class = E_BOOK_BACKEND_CLASS (klass);
2518 	book_backend_class->impl_get_backend_property = ebmb_get_backend_property;
2519 	book_backend_class->impl_start_view = ebmb_start_view;
2520 	book_backend_class->impl_stop_view = ebmb_stop_view;
2521 	book_backend_class->impl_get_direct_book = ebmb_get_direct_book;
2522 	book_backend_class->impl_configure_direct = ebmb_configure_direct;
2523 	book_backend_class->impl_set_locale = ebmb_set_locale;
2524 	book_backend_class->impl_dup_locale = ebmb_dup_locale;
2525 	book_backend_class->impl_create_cursor = ebmb_create_cursor;
2526 	book_backend_class->impl_delete_cursor = ebmb_delete_cursor;
2527 
2528 	backend_class = E_BACKEND_CLASS (klass);
2529 	backend_class->authenticate_sync = ebmb_authenticate_sync;
2530 
2531 	object_class = G_OBJECT_CLASS (klass);
2532 	object_class->set_property = e_book_meta_backend_set_property;
2533 	object_class->get_property = e_book_meta_backend_get_property;
2534 	object_class->constructed = e_book_meta_backend_constructed;
2535 	object_class->dispose = e_book_meta_backend_dispose;
2536 	object_class->finalize = e_book_meta_backend_finalize;
2537 
2538 	/**
2539 	 * EBookMetaBackend:cache:
2540 	 *
2541 	 * The #EBookCache being used for this meta backend.
2542 	 **/
2543 	g_object_class_install_property (
2544 		object_class,
2545 		PROP_CACHE,
2546 		g_param_spec_object (
2547 			"cache",
2548 			"Cache",
2549 			"Book Cache",
2550 			E_TYPE_BOOK_CACHE,
2551 			G_PARAM_READWRITE |
2552 			G_PARAM_EXPLICIT_NOTIFY |
2553 			G_PARAM_STATIC_STRINGS));
2554 
2555 	/* This signal is meant for testing purposes mainly */
2556 	signals[REFRESH_COMPLETED] = g_signal_new (
2557 		"refresh-completed",
2558 		G_OBJECT_CLASS_TYPE (klass),
2559 		G_SIGNAL_RUN_LAST,
2560 		0,
2561 		NULL, NULL, NULL,
2562 		G_TYPE_NONE, 0, G_TYPE_NONE);
2563 
2564 	/**
2565 	 * EBookMetaBackend::source-changed
2566 	 *
2567 	 * This signal is emitted whenever the underlying backend #ESource
2568 	 * changes. Unlike the #ESource's 'changed' signal this one is
2569 	 * tight to the #EBookMetaBackend itself and is emitted from
2570 	 * a dedicated thread, thus it doesn't block the main thread.
2571 	 *
2572 	 * Since: 3.26
2573 	 **/
2574 	signals[SOURCE_CHANGED] = g_signal_new (
2575 		"source-changed",
2576 		G_OBJECT_CLASS_TYPE (klass),
2577 		G_SIGNAL_RUN_LAST,
2578 		G_STRUCT_OFFSET (EBookMetaBackendClass, source_changed),
2579 		NULL, NULL, NULL,
2580 		G_TYPE_NONE, 0, G_TYPE_NONE);
2581 }
2582 
2583 static void
e_book_meta_backend_init(EBookMetaBackend * meta_backend)2584 e_book_meta_backend_init (EBookMetaBackend *meta_backend)
2585 {
2586 	meta_backend->priv = e_book_meta_backend_get_instance_private (meta_backend);
2587 
2588 	g_mutex_init (&meta_backend->priv->connect_lock);
2589 	g_mutex_init (&meta_backend->priv->property_lock);
2590 	g_mutex_init (&meta_backend->priv->wait_credentials_lock);
2591 	g_cond_init (&meta_backend->priv->wait_credentials_cond);
2592 
2593 	meta_backend->priv->view_cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
2594 	meta_backend->priv->current_online_state = FALSE;
2595 	meta_backend->priv->refresh_after_authenticate = FALSE;
2596 	meta_backend->priv->ever_connected = -1;
2597 	meta_backend->priv->connected_writable = -1;
2598 }
2599 
2600 /**
2601  * e_book_meta_backend_get_capabilities:
2602  * @meta_backend: an #EBookMetaBackend
2603  *
2604  * Returns: an #EBookBackend::capabilities property to be used by
2605  *    the descendant in conjunction to the descendant's capabilities
2606  *    in the result of e_book_backend_get_backend_property() with
2607  *    #CLIENT_BACKEND_PROPERTY_CAPABILITIES.
2608  *
2609  * Since: 3.26
2610  **/
2611 const gchar *
e_book_meta_backend_get_capabilities(EBookMetaBackend * meta_backend)2612 e_book_meta_backend_get_capabilities (EBookMetaBackend *meta_backend)
2613 {
2614 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
2615 
2616 	return "refresh-supported" ","
2617 		"bulk-adds" ","
2618 		"bulk-modifies" ","
2619 		"bulk-removes";
2620 }
2621 
2622 /**
2623  * e_book_meta_backend_set_ever_connected:
2624  * @meta_backend: an #EBookMetaBackend
2625  * @value: value to set
2626  *
2627  * Sets whether the @meta_backend ever made a successful connection
2628  * to its destination.
2629  *
2630  * This is used by the @meta_backend itself, during the opening phase,
2631  * when it had not been connected yet, then it does so immediately, to
2632  * eventually report settings error easily.
2633  *
2634  * Since: 3.26
2635  **/
2636 void
e_book_meta_backend_set_ever_connected(EBookMetaBackend * meta_backend,gboolean value)2637 e_book_meta_backend_set_ever_connected (EBookMetaBackend *meta_backend,
2638 					gboolean value)
2639 {
2640 	EBookCache *book_cache;
2641 
2642 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2643 
2644 	if ((value ? 1 : 0) == meta_backend->priv->ever_connected)
2645 		return;
2646 
2647 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
2648 	meta_backend->priv->ever_connected = value ? 1 : 0;
2649 	e_cache_set_key_int (E_CACHE (book_cache), EBMB_KEY_EVER_CONNECTED, meta_backend->priv->ever_connected, NULL);
2650 	g_clear_object (&book_cache);
2651 }
2652 
2653 /**
2654  * e_book_meta_backend_get_ever_connected:
2655  * @meta_backend: an #EBookMetaBackend
2656  *
2657  * Returns: Whether the @meta_backend ever made a successful connection
2658  *    to its destination.
2659  *
2660  * Since: 3.26
2661  **/
2662 gboolean
e_book_meta_backend_get_ever_connected(EBookMetaBackend * meta_backend)2663 e_book_meta_backend_get_ever_connected (EBookMetaBackend *meta_backend)
2664 {
2665 	gboolean result;
2666 
2667 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
2668 
2669 	if (meta_backend->priv->ever_connected == -1) {
2670 		EBookCache *book_cache;
2671 
2672 		book_cache = e_book_meta_backend_ref_cache (meta_backend);
2673 		result = e_cache_get_key_int (E_CACHE (book_cache), EBMB_KEY_EVER_CONNECTED, NULL) == 1;
2674 		g_clear_object (&book_cache);
2675 
2676 		meta_backend->priv->ever_connected = result ? 1 : 0;
2677 	} else {
2678 		result = meta_backend->priv->ever_connected == 1;
2679 	}
2680 
2681 	return result;
2682 }
2683 
2684 /**
2685  * e_book_meta_backend_set_connected_writable:
2686  * @meta_backend: an #EBookMetaBackend
2687  * @value: value to set
2688  *
2689  * Sets whether the @meta_backend connected to a writable destination.
2690  * This value has meaning only if e_book_meta_backend_get_ever_connected()
2691  * is %TRUE.
2692  *
2693  * This is used by the @meta_backend itself, during the opening phase,
2694  * to set the backend writable or not also in the offline mode.
2695  *
2696  * Since: 3.26
2697  **/
2698 void
e_book_meta_backend_set_connected_writable(EBookMetaBackend * meta_backend,gboolean value)2699 e_book_meta_backend_set_connected_writable (EBookMetaBackend *meta_backend,
2700 					    gboolean value)
2701 {
2702 	EBookCache *book_cache;
2703 
2704 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2705 
2706 	if ((value ? 1 : 0) == meta_backend->priv->connected_writable)
2707 		return;
2708 
2709 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
2710 	meta_backend->priv->connected_writable = value ? 1 : 0;
2711 	e_cache_set_key_int (E_CACHE (book_cache), EBMB_KEY_CONNECTED_WRITABLE, meta_backend->priv->connected_writable, NULL);
2712 	g_clear_object (&book_cache);
2713 }
2714 
2715 /**
2716  * e_book_meta_backend_get_connected_writable:
2717  * @meta_backend: an #EBookMetaBackend
2718  *
2719  * This value has meaning only if e_book_meta_backend_get_ever_connected()
2720  * is %TRUE.
2721  *
2722  * Returns: Whether the @meta_backend connected to a writable destination.
2723  *
2724  * Since: 3.26
2725  **/
2726 gboolean
e_book_meta_backend_get_connected_writable(EBookMetaBackend * meta_backend)2727 e_book_meta_backend_get_connected_writable (EBookMetaBackend *meta_backend)
2728 {
2729 	gboolean result;
2730 
2731 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
2732 
2733 	if (meta_backend->priv->connected_writable == -1) {
2734 		EBookCache *book_cache;
2735 
2736 		book_cache = e_book_meta_backend_ref_cache (meta_backend);
2737 		result = e_cache_get_key_int (E_CACHE (book_cache), EBMB_KEY_CONNECTED_WRITABLE, NULL) == 1;
2738 		g_clear_object (&book_cache);
2739 
2740 		meta_backend->priv->connected_writable = result ? 1 : 0;
2741 	} else {
2742 		result = meta_backend->priv->connected_writable == 1;
2743 	}
2744 
2745 	return result;
2746 }
2747 
2748 /**
2749  * e_book_meta_backend_dup_sync_tag:
2750  * @meta_backend: an #EBookMetaBackend
2751  *
2752  * Returns the last known synchronization tag, the same as used to
2753  * call e_book_meta_backend_get_changes_sync().
2754  *
2755  * Free the returned string with g_free(), when no longer needed.
2756  *
2757  * Returns: (transfer full) (nullable): The last known synchronization tag,
2758  *    or %NULL, when none is stored.
2759  *
2760  * Since: 3.28
2761  **/
2762 gchar *
e_book_meta_backend_dup_sync_tag(EBookMetaBackend * meta_backend)2763 e_book_meta_backend_dup_sync_tag (EBookMetaBackend *meta_backend)
2764 {
2765 	EBookCache *book_cache;
2766 	gchar *sync_tag;
2767 
2768 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
2769 
2770 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
2771 	if (!book_cache)
2772 		return NULL;
2773 
2774 	sync_tag = e_cache_dup_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, NULL);
2775 	if (sync_tag && !*sync_tag) {
2776 		g_free (sync_tag);
2777 		sync_tag = NULL;
2778 	}
2779 
2780 	g_clear_object (&book_cache);
2781 
2782 	return sync_tag;
2783 }
2784 
2785 static void
ebmb_cache_revision_changed_cb(ECache * cache,gpointer user_data)2786 ebmb_cache_revision_changed_cb (ECache *cache,
2787 				gpointer user_data)
2788 {
2789 	EBookMetaBackend *meta_backend = user_data;
2790 	gchar *revision;
2791 
2792 	g_return_if_fail (E_IS_CACHE (cache));
2793 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2794 
2795 	revision = e_cache_dup_revision (cache);
2796 	if (revision) {
2797 		e_book_backend_notify_property_changed (E_BOOK_BACKEND (meta_backend),
2798 			E_BOOK_BACKEND_PROPERTY_REVISION, revision);
2799 		g_free (revision);
2800 	}
2801 }
2802 
2803 /**
2804  * e_book_meta_backend_set_cache:
2805  * @meta_backend: an #EBookMetaBackend
2806  * @cache: an #EBookCache to use
2807  *
2808  * Sets the @cache as the cache to be used by the @meta_backend.
2809  * By default, a cache.db in EBookBackend::cache-dir is created
2810  * in the constructed method. This function can be used to override
2811  * the default.
2812  *
2813  * Note the @meta_backend adds its own reference to the @cache.
2814  *
2815  * Since: 3.26
2816  **/
2817 void
e_book_meta_backend_set_cache(EBookMetaBackend * meta_backend,EBookCache * cache)2818 e_book_meta_backend_set_cache (EBookMetaBackend *meta_backend,
2819 			       EBookCache *cache)
2820 {
2821 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
2822 	g_return_if_fail (E_IS_BOOK_CACHE (cache));
2823 
2824 	g_mutex_lock (&meta_backend->priv->property_lock);
2825 
2826 	if (meta_backend->priv->cache == cache) {
2827 		g_mutex_unlock (&meta_backend->priv->property_lock);
2828 		return;
2829 	}
2830 
2831 	g_clear_error (&meta_backend->priv->create_cache_error);
2832 
2833 	if (meta_backend->priv->cache) {
2834 		g_signal_handler_disconnect (meta_backend->priv->cache,
2835 			meta_backend->priv->revision_changed_id);
2836 	}
2837 
2838 	g_clear_object (&meta_backend->priv->cache);
2839 	meta_backend->priv->cache = g_object_ref (cache);
2840 
2841 	meta_backend->priv->revision_changed_id = g_signal_connect_object (meta_backend->priv->cache,
2842 		"revision-changed", G_CALLBACK (ebmb_cache_revision_changed_cb), meta_backend, 0);
2843 
2844 	g_mutex_unlock (&meta_backend->priv->property_lock);
2845 
2846 	g_object_notify (G_OBJECT (meta_backend), "cache");
2847 }
2848 
2849 /**
2850  * e_book_meta_backend_ref_cache:
2851  * @meta_backend: an #EBookMetaBackend
2852  *
2853  * Returns: (transfer full): Referenced #EBookCache, which is used by @meta_backend.
2854  *    Unref it with g_object_unref(), when no longer needed.
2855  *
2856  * Since: 3.26
2857  **/
2858 EBookCache *
e_book_meta_backend_ref_cache(EBookMetaBackend * meta_backend)2859 e_book_meta_backend_ref_cache (EBookMetaBackend *meta_backend)
2860 {
2861 	EBookCache *cache;
2862 
2863 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
2864 
2865 	g_mutex_lock (&meta_backend->priv->property_lock);
2866 
2867 	if (meta_backend->priv->cache)
2868 		cache = g_object_ref (meta_backend->priv->cache);
2869 	else
2870 		cache = NULL;
2871 
2872 	g_mutex_unlock (&meta_backend->priv->property_lock);
2873 
2874 	return cache;
2875 }
2876 
2877 static gchar *
ebmb_get_mime_type(const gchar * url,const gchar * content,gsize content_len)2878 ebmb_get_mime_type (const gchar *url,
2879 		    const gchar *content,
2880 		    gsize content_len)
2881 {
2882 	gchar *content_type, *filename = NULL, *mime_type = NULL;
2883 
2884 	if (url) {
2885 		filename = g_filename_from_uri (url, NULL, NULL);
2886 		if (filename) {
2887 			gchar *extension;
2888 
2889 			/* When storing inline attachments to the local file,
2890 			   the file extension is the mime type as stored in the attribute */
2891 			extension = strrchr (filename, '.');
2892 			if (extension)
2893 				extension++;
2894 
2895 			if (extension) {
2896 				mime_type = g_uri_unescape_string (extension, NULL);
2897 				if (mime_type && !strchr (mime_type, '/')) {
2898 					gchar *tmp;
2899 
2900 					tmp = g_strconcat ("image/", mime_type, NULL);
2901 
2902 					g_free (mime_type);
2903 					mime_type = tmp;
2904 				}
2905 
2906 				content_type = g_content_type_from_mime_type (mime_type);
2907 
2908 				if (!content_type) {
2909 					g_free (mime_type);
2910 					mime_type = NULL;
2911 				}
2912 
2913 				g_free (content_type);
2914 			}
2915 		}
2916 	}
2917 
2918 	if (!mime_type) {
2919 		content_type = g_content_type_guess (filename, (const guchar *) content, content_len, NULL);
2920 
2921 		if (content_type)
2922 			mime_type = g_content_type_get_mime_type (content_type);
2923 
2924 		g_free (content_type);
2925 	}
2926 
2927 	g_free (filename);
2928 
2929 	return mime_type;
2930 }
2931 
2932 /**
2933  * e_book_meta_backend_inline_local_photos_sync:
2934  * @meta_backend: an #EBookMetaBackend
2935  * @contact: an #EContact to work with
2936  * @cancellable: optional #GCancellable object, or %NULL
2937  * @error: return location for a #GError, or %NULL
2938  *
2939  * Changes all URL photos and logos which point to a local file in @contact
2940  * to inline type, aka adds the file content into the @contact.
2941  * This is called automatically before e_book_meta_backend_save_contact_sync().
2942  *
2943  * The reverse operation is e_book_meta_backend_store_inline_photos_sync().
2944  *
2945  * Returns: Whether succeeded.
2946  *
2947  * Since: 3.26
2948  **/
2949 gboolean
e_book_meta_backend_inline_local_photos_sync(EBookMetaBackend * meta_backend,EContact * contact,GCancellable * cancellable,GError ** error)2950 e_book_meta_backend_inline_local_photos_sync (EBookMetaBackend *meta_backend,
2951 					      EContact *contact,
2952 					      GCancellable *cancellable,
2953 					      GError **error)
2954 {
2955 	GList *attributes, *link;
2956 	gboolean success = TRUE;
2957 
2958 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
2959 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
2960 
2961 	attributes = e_vcard_get_attributes (E_VCARD (contact));
2962 
2963 	for (link = attributes; link; link = g_list_next (link)) {
2964 		EVCardAttribute *attr = link->data;
2965 		const gchar *attr_name;
2966 		GList *values;
2967 
2968 		attr_name = e_vcard_attribute_get_name (attr);
2969 		if (!attr_name || (
2970 		    g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 &&
2971 		    g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) {
2972 			continue;
2973 		}
2974 
2975 		values = e_vcard_attribute_get_param (attr, EVC_VALUE);
2976 		if (values && g_ascii_strcasecmp (values->data, "uri") == 0) {
2977 			gchar *url;
2978 
2979 			url = e_vcard_attribute_get_value (attr);
2980 			if (url && g_str_has_prefix (url, LOCAL_PREFIX)) {
2981 				GFile *file;
2982 				gchar *basename;
2983 				gchar *content;
2984 				gsize len;
2985 
2986 				file = g_file_new_for_uri (url);
2987 				basename = g_file_get_basename (file);
2988 				if (g_file_load_contents (file, cancellable, &content, &len, NULL, error)) {
2989 					gchar *mime_type;
2990 					const gchar *image_type, *pp;
2991 
2992 					mime_type = ebmb_get_mime_type (url, content, len);
2993 					if (mime_type && (pp = strchr (mime_type, '/'))) {
2994 						image_type = pp + 1;
2995 					} else {
2996 						image_type = "X-EVOLUTION-UNKNOWN";
2997 					}
2998 
2999 					e_vcard_attribute_remove_param (attr, EVC_TYPE);
3000 					e_vcard_attribute_remove_param (attr, EVC_ENCODING);
3001 					e_vcard_attribute_remove_param (attr, EVC_VALUE);
3002 					e_vcard_attribute_remove_values (attr);
3003 
3004 					e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE), image_type);
3005 					e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_ENCODING), "b");
3006 					e_vcard_attribute_add_value_decoded (attr, content, len);
3007 
3008 					g_free (mime_type);
3009 					g_free (content);
3010 				} else {
3011 					success = FALSE;
3012 				}
3013 
3014 				g_object_unref (file);
3015 				g_free (basename);
3016 			}
3017 
3018 			g_free (url);
3019 		}
3020 	}
3021 
3022 	return success;
3023 }
3024 
3025 static gchar *
ebmb_create_photo_local_filename(EBookMetaBackend * meta_backend,const gchar * uid,const gchar * attr_name,gint fileindex,const gchar * type)3026 ebmb_create_photo_local_filename (EBookMetaBackend *meta_backend,
3027 				  const gchar *uid,
3028 				  const gchar *attr_name,
3029 				  gint fileindex,
3030 				  const gchar *type)
3031 {
3032 	EBookCache *book_cache;
3033 	gchar *local_filename, *cache_path, *checksum, *prefix, *extension, *filename;
3034 
3035 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL);
3036 	g_return_val_if_fail (uid != NULL, NULL);
3037 	g_return_val_if_fail (attr_name != NULL, NULL);
3038 
3039 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
3040 	g_return_val_if_fail (book_cache != NULL, NULL);
3041 
3042 	cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache)));
3043 	checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1, uid, -1);
3044 	prefix = g_strdup_printf ("%s-%s-%d", attr_name, checksum, fileindex);
3045 
3046 	if (type && *type)
3047 		extension = g_uri_escape_string (type, NULL, TRUE);
3048 	else
3049 		extension = NULL;
3050 
3051 	filename = g_strconcat (prefix, extension ? "." : NULL, extension, NULL);
3052 
3053 	local_filename = g_build_filename (cache_path, filename, NULL);
3054 
3055 	g_object_unref (book_cache);
3056 	g_free (cache_path);
3057 	g_free (checksum);
3058 	g_free (prefix);
3059 	g_free (extension);
3060 	g_free (filename);
3061 
3062 	return local_filename;
3063 }
3064 
3065 /**
3066  * e_book_meta_backend_store_inline_photos_sync:
3067  * @meta_backend: an #EBookMetaBackend
3068  * @contact: an #EContact to work with
3069  * @cancellable: optional #GCancellable object, or %NULL
3070  * @error: return location for a #GError, or %NULL
3071  *
3072  * Changes all inline photos and logos to URL type in @contact, which
3073  * will point to a local file instead, beside the cache file.
3074  * This is called automatically after e_book_meta_backend_load_contact_sync().
3075  *
3076  * The reverse operation is e_book_meta_backend_inline_local_photos_sync().
3077  *
3078  * Returns: Whether succeeded.
3079  *
3080  * Since: 3.26
3081  **/
3082 gboolean
e_book_meta_backend_store_inline_photos_sync(EBookMetaBackend * meta_backend,EContact * contact,GCancellable * cancellable,GError ** error)3083 e_book_meta_backend_store_inline_photos_sync (EBookMetaBackend *meta_backend,
3084 					      EContact *contact,
3085 					      GCancellable *cancellable,
3086 					      GError **error)
3087 {
3088 	gint fileindex;
3089 	GList *attributes, *link;
3090 	gboolean success = TRUE;
3091 
3092 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3093 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
3094 
3095 	attributes = e_vcard_get_attributes (E_VCARD (contact));
3096 
3097 	for (link = attributes, fileindex = 0; link; link = g_list_next (link), fileindex++) {
3098 		EVCardAttribute *attr = link->data;
3099 		const gchar *attr_name;
3100 		GList *values;
3101 
3102 		attr_name = e_vcard_attribute_get_name (attr);
3103 		if (!attr_name || (
3104 		    g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 &&
3105 		    g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) {
3106 			continue;
3107 		}
3108 
3109 		values = e_vcard_attribute_get_param (attr, EVC_ENCODING);
3110 		if (values && (g_ascii_strcasecmp (values->data, "b") == 0 || g_ascii_strcasecmp (values->data, "base64") == 0)) {
3111 			values = e_vcard_attribute_get_values_decoded (attr);
3112 			if (values && values->data) {
3113 				const GString *decoded = values->data;
3114 				gchar *local_filename;
3115 
3116 				if (!decoded->len)
3117 					continue;
3118 
3119 				values = e_vcard_attribute_get_param (attr, EVC_TYPE);
3120 
3121 				local_filename = ebmb_create_photo_local_filename (meta_backend, e_contact_get_const (contact, E_CONTACT_UID),
3122 					attr_name, fileindex, values ? values->data : NULL);
3123 				if (local_filename &&
3124 				    g_file_set_contents (local_filename, decoded->str, decoded->len, error)) {
3125 					gchar *url;
3126 
3127 					e_vcard_attribute_remove_param (attr, EVC_TYPE);
3128 					e_vcard_attribute_remove_param (attr, EVC_ENCODING);
3129 					e_vcard_attribute_remove_param (attr, EVC_VALUE);
3130 					e_vcard_attribute_remove_values (attr);
3131 
3132 					url = g_filename_to_uri (local_filename, NULL, NULL);
3133 
3134 					e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_VALUE), "uri");
3135 					e_vcard_attribute_add_value (attr, url);
3136 
3137 					g_free (url);
3138 				} else {
3139 					success = FALSE;
3140 				}
3141 
3142 				g_free (local_filename);
3143 			}
3144 		}
3145 	}
3146 
3147 	return success;
3148 }
3149 
3150 /**
3151  * e_book_meta_backend_empty_cache_sync:
3152  * @meta_backend: an #EBookMetaBackend
3153  * @cancellable: optional #GCancellable object, or %NULL
3154  * @error: return location for a #GError, or %NULL
3155  *
3156  * Empties the local cache by removing all known contacts from it
3157  * and notifies about such removal any opened views.
3158  *
3159  * Returns: Whether succeeded.
3160  *
3161  * Since: 3.26
3162  **/
3163 gboolean
e_book_meta_backend_empty_cache_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)3164 e_book_meta_backend_empty_cache_sync (EBookMetaBackend *meta_backend,
3165 				      GCancellable *cancellable,
3166 				      GError **error)
3167 {
3168 	EBookBackend *book_backend;
3169 	EBookCache *book_cache;
3170 	GSList *uids = NULL, *link;
3171 	gchar *cache_path, *cache_filename;
3172 	gboolean success;
3173 
3174 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3175 
3176 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
3177 	g_return_val_if_fail (book_cache != NULL, FALSE);
3178 
3179 	e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_WRITE);
3180 
3181 	book_backend = E_BOOK_BACKEND (meta_backend);
3182 
3183 	success = e_book_cache_search_uids (book_cache, NULL, &uids, cancellable, error);
3184 	if (success)
3185 		success = e_cache_remove_all (E_CACHE (book_cache), cancellable, error);
3186 
3187 	e_cache_unlock (E_CACHE (book_cache), success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
3188 
3189 	cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache)));
3190 	cache_filename = g_path_get_basename (e_cache_get_filename (E_CACHE (book_cache)));
3191 
3192 	g_object_unref (book_cache);
3193 
3194 	if (success) {
3195 		GDir *dir;
3196 
3197 		for (link = uids; link; link = g_slist_next (link)) {
3198 			const gchar *uid = link->data;
3199 
3200 			if (!uid)
3201 				continue;
3202 
3203 			e_book_backend_notify_remove (book_backend, uid);
3204 		}
3205 
3206 		g_mutex_lock (&meta_backend->priv->property_lock);
3207 
3208 		for (link = meta_backend->priv->cursors; link; link = g_slist_next (link)) {
3209 			EDataBookCursor *cursor = link->data;
3210 
3211 			e_data_book_cursor_recalculate (cursor, cancellable, NULL);
3212 		}
3213 
3214 		g_mutex_unlock (&meta_backend->priv->property_lock);
3215 
3216 		/* Remove also all photos and logos stored beside the cache */
3217 		dir = g_dir_open (cache_path, 0, NULL);
3218 		if (dir) {
3219 			const gchar *filename;
3220 
3221 			while (filename = g_dir_read_name (dir), filename) {
3222 				if ((g_str_has_prefix (filename, EVC_PHOTO) ||
3223 				    g_str_has_prefix (filename, EVC_LOGO)) &&
3224 				    g_strcmp0 (cache_filename, filename) != 0) {
3225 					if (g_unlink (filename) == -1) {
3226 						/* Something failed, ignore the error */
3227 					}
3228 				}
3229 			}
3230 
3231 			g_dir_close (dir);
3232 		}
3233 	}
3234 
3235 	g_slist_free_full (uids, g_free);
3236 	g_free (cache_filename);
3237 	g_free (cache_path);
3238 
3239 	return success;
3240 }
3241 
3242 /**
3243  * e_book_meta_backend_schedule_refresh:
3244  * @meta_backend: an #EBookMetaBackend
3245  *
3246  * Schedules refresh of the content of the @meta_backend. If there's any
3247  * already scheduled, then the function does nothing.
3248  *
3249  * Use e_book_meta_backend_refresh_sync() to refresh the @meta_backend
3250  * immediately.
3251  *
3252  * Since: 3.26
3253  **/
3254 void
e_book_meta_backend_schedule_refresh(EBookMetaBackend * meta_backend)3255 e_book_meta_backend_schedule_refresh (EBookMetaBackend *meta_backend)
3256 {
3257 	GCancellable *cancellable;
3258 
3259 	g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend));
3260 
3261 	g_mutex_lock (&meta_backend->priv->property_lock);
3262 
3263 	if (meta_backend->priv->refresh_cancellable) {
3264 		/* Already refreshing the content */
3265 		g_mutex_unlock (&meta_backend->priv->property_lock);
3266 		return;
3267 	}
3268 
3269 	cancellable = g_cancellable_new ();
3270 	meta_backend->priv->refresh_cancellable = g_object_ref (cancellable);
3271 
3272 	g_mutex_unlock (&meta_backend->priv->property_lock);
3273 
3274 	e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable,
3275 		ebmb_refresh_thread_func, NULL, NULL);
3276 
3277 	g_object_unref (cancellable);
3278 }
3279 
3280 /**
3281  * e_book_meta_backend_refresh_sync:
3282  * @meta_backend: an #EBookMetaBackend
3283  * @cancellable: optional #GCancellable object, or %NULL
3284  * @error: return location for a #GError, or %NULL
3285  *
3286  * Refreshes the @meta_backend immediately. To just schedule refresh
3287  * operation call e_book_meta_backend_schedule_refresh().
3288  *
3289  * Returns: Whether succeeded.
3290  *
3291  * Since: 3.26
3292  **/
3293 gboolean
e_book_meta_backend_refresh_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)3294 e_book_meta_backend_refresh_sync (EBookMetaBackend *meta_backend,
3295 				  GCancellable *cancellable,
3296 				  GError **error)
3297 {
3298 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3299 
3300 	return ebmb_refresh_internal_sync (meta_backend, TRUE, cancellable, error);
3301 }
3302 
3303 /**
3304  * e_book_meta_backend_ensure_connected_sync:
3305  * @meta_backend: an #EBookMetaBackend
3306  * @cancellable: optional #GCancellable object, or %NULL
3307  * @error: return location for a #GError, or %NULL
3308  *
3309  * Ensures that the @meta_backend is connected to its destination.
3310  *
3311  * Returns: Whether succeeded.
3312  *
3313  * Since: 3.26
3314  **/
3315 gboolean
e_book_meta_backend_ensure_connected_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)3316 e_book_meta_backend_ensure_connected_sync (EBookMetaBackend *meta_backend,
3317 					   GCancellable *cancellable,
3318 					   GError **error)
3319 {
3320 	EBackend *backend;
3321 	ENamedParameters *credentials;
3322 	ESource *source;
3323 	ESourceAuthenticationResult auth_result = E_SOURCE_AUTHENTICATION_UNKNOWN;
3324 	ESourceCredentialsReason creds_reason = E_SOURCE_CREDENTIALS_REASON_ERROR;
3325 	gchar *certificate_pem = NULL;
3326 	GTlsCertificateFlags certificate_errors = 0;
3327 	GError *local_error = NULL;
3328 
3329 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3330 
3331 	backend = E_BACKEND (meta_backend);
3332 
3333 	if (!e_backend_get_online (backend) &&
3334 	    e_backend_is_destination_reachable (backend, cancellable, NULL))
3335 		e_backend_set_online (backend, TRUE);
3336 
3337 	if (!e_backend_get_online (backend)) {
3338 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_REPOSITORY_OFFLINE, NULL));
3339 
3340 		return FALSE;
3341 	}
3342 
3343 	g_mutex_lock (&meta_backend->priv->property_lock);
3344 	credentials = e_named_parameters_new_clone (meta_backend->priv->last_credentials);
3345 	g_mutex_unlock (&meta_backend->priv->property_lock);
3346 
3347 	g_mutex_lock (&meta_backend->priv->connect_lock);
3348 
3349 	source = e_backend_get_source (backend);
3350 
3351 	if (e_source_get_connection_status (source) != E_SOURCE_CONNECTION_STATUS_CONNECTED)
3352 		e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
3353 
3354 	if (e_book_meta_backend_connect_sync (meta_backend, credentials, &auth_result, &certificate_pem, &certificate_errors,
3355 		cancellable, &local_error)) {
3356 		ebmb_update_connection_values (meta_backend);
3357 		e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
3358 		g_mutex_unlock (&meta_backend->priv->connect_lock);
3359 		e_named_parameters_free (credentials);
3360 
3361 		return TRUE;
3362 	}
3363 
3364 	e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
3365 
3366 	g_mutex_unlock (&meta_backend->priv->connect_lock);
3367 
3368 	e_named_parameters_free (credentials);
3369 
3370 	g_warn_if_fail (auth_result != E_SOURCE_AUTHENTICATION_ACCEPTED);
3371 
3372 	if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) {
3373 		e_backend_set_online (backend, FALSE);
3374 		g_propagate_error (error, local_error);
3375 		g_free (certificate_pem);
3376 
3377 		return FALSE;
3378 	}
3379 
3380 	switch (auth_result) {
3381 	case E_SOURCE_AUTHENTICATION_UNKNOWN:
3382 		if (local_error)
3383 			g_propagate_error (error, local_error);
3384 		g_free (certificate_pem);
3385 		return FALSE;
3386 	case E_SOURCE_AUTHENTICATION_ERROR:
3387 		creds_reason = E_SOURCE_CREDENTIALS_REASON_ERROR;
3388 		break;
3389 	case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
3390 		creds_reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
3391 		break;
3392 	case E_SOURCE_AUTHENTICATION_ACCEPTED:
3393 		g_warn_if_reached ();
3394 		break;
3395 	case E_SOURCE_AUTHENTICATION_REJECTED:
3396 		creds_reason = E_SOURCE_CREDENTIALS_REASON_REJECTED;
3397 		break;
3398 	case E_SOURCE_AUTHENTICATION_REQUIRED:
3399 		creds_reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
3400 		break;
3401 	}
3402 
3403 	e_backend_schedule_credentials_required (backend, creds_reason, certificate_pem, certificate_errors,
3404 		local_error, cancellable, G_STRFUNC);
3405 
3406 	g_clear_error (&local_error);
3407 	g_free (certificate_pem);
3408 
3409 	return FALSE;
3410 }
3411 
3412 /**
3413  * e_book_meta_backend_split_changes_sync:
3414  * @meta_backend: an #EBookMetaBackend
3415  * @objects: (inout) (element-type EBookMetaBackendInfo):
3416  *    a #GSList of #EBookMetaBackendInfo object infos to split
3417  * @out_created_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3418  *    a #GSList of #EBookMetaBackendInfo object infos which had been created
3419  * @out_modified_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3420  *    a #GSList of #EBookMetaBackendInfo object infos which had been modified
3421  * @out_removed_objects: (out) (element-type EBookMetaBackendInfo) (transfer full) (nullable):
3422  *    a #GSList of #EBookMetaBackendInfo object infos which had been removed;
3423  *    it can be %NULL, to not gather list of removed object infos
3424  * @cancellable: optional #GCancellable object, or %NULL
3425  * @error: return location for a #GError, or %NULL
3426  *
3427  * Splits @objects into created/modified/removed lists according to current local
3428  * cache content. Only the @out_removed_objects can be %NULL, others cannot.
3429  * The function modifies @objects by moving its 'data' to corresponding out
3430  * lists and sets the @objects 'data' to %NULL.
3431  *
3432  * Each output #GSList should be freed with
3433  * g_slist_free_full (objects, e_book_meta_backend_info_free);
3434  * when no longer needed.
3435  *
3436  * The caller is still responsible to free @objects as well.
3437  *
3438  * Returns: Whether succeeded.
3439  *
3440  * Since: 3.26
3441  **/
3442 gboolean
e_book_meta_backend_split_changes_sync(EBookMetaBackend * meta_backend,GSList * objects,GSList ** out_created_objects,GSList ** out_modified_objects,GSList ** out_removed_objects,GCancellable * cancellable,GError ** error)3443 e_book_meta_backend_split_changes_sync (EBookMetaBackend *meta_backend,
3444 					GSList *objects,
3445 					GSList **out_created_objects,
3446 					GSList **out_modified_objects,
3447 					GSList **out_removed_objects,
3448 					GCancellable *cancellable,
3449 					GError **error)
3450 {
3451 	GHashTable *locally_cached; /* EContactId * ~> gchar *revision */
3452 	GHashTableIter iter;
3453 	GSList *link;
3454 	EBookCache *book_cache;
3455 	gpointer key, value;
3456 
3457 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3458 	g_return_val_if_fail (out_created_objects, FALSE);
3459 	g_return_val_if_fail (out_modified_objects, FALSE);
3460 
3461 	*out_created_objects = NULL;
3462 	*out_modified_objects = NULL;
3463 
3464 	if (out_removed_objects)
3465 		*out_removed_objects = NULL;
3466 
3467 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
3468 	g_return_val_if_fail (book_cache != NULL, FALSE);
3469 
3470 	locally_cached = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
3471 
3472 	if (!e_book_cache_search_with_callback (book_cache, NULL,
3473 		ebmb_gather_locally_cached_objects_cb, locally_cached, cancellable, error)) {
3474 		g_hash_table_destroy (locally_cached);
3475 		g_object_unref (book_cache);
3476 		return FALSE;
3477 	}
3478 
3479 	for (link = objects; link; link = g_slist_next (link)) {
3480 		EBookMetaBackendInfo *nfo = link->data;
3481 
3482 		if (!nfo)
3483 			continue;
3484 
3485 		if (!g_hash_table_contains (locally_cached, nfo->uid)) {
3486 			link->data = NULL;
3487 
3488 			*out_created_objects = g_slist_prepend (*out_created_objects, nfo);
3489 		} else {
3490 			const gchar *local_revision = g_hash_table_lookup (locally_cached, nfo->uid);
3491 
3492 			if (g_strcmp0 (local_revision, nfo->revision) != 0) {
3493 				link->data = NULL;
3494 
3495 				*out_modified_objects = g_slist_prepend (*out_modified_objects, nfo);
3496 			}
3497 
3498 			g_hash_table_remove (locally_cached, nfo->uid);
3499 		}
3500 	}
3501 
3502 	if (out_removed_objects) {
3503 		/* What left in the hash table is removed from the remote side */
3504 		g_hash_table_iter_init (&iter, locally_cached);
3505 		while (g_hash_table_iter_next (&iter, &key, &value)) {
3506 			const gchar *uid = key;
3507 			const gchar *revision = value;
3508 			EBookMetaBackendInfo *nfo;
3509 
3510 			if (!uid) {
3511 				g_warn_if_reached ();
3512 				continue;
3513 			}
3514 
3515 			nfo = e_book_meta_backend_info_new (uid, revision, NULL, NULL);
3516 			*out_removed_objects = g_slist_prepend (*out_removed_objects, nfo);
3517 		}
3518 
3519 		*out_removed_objects = g_slist_reverse (*out_removed_objects);
3520 	}
3521 
3522 	g_hash_table_destroy (locally_cached);
3523 	g_object_unref (book_cache);
3524 
3525 	*out_created_objects = g_slist_reverse (*out_created_objects);
3526 	*out_modified_objects = g_slist_reverse (*out_modified_objects);
3527 
3528 	return TRUE;
3529 }
3530 
3531 /**
3532  * e_book_meta_backend_process_changes_sync:
3533  * @meta_backend: an #EBookMetaBackend
3534  * @created_objects: (element-type EBookMetaBackendInfo) (nullable):
3535  *    a #GSList of #EBookMetaBackendInfo object infos which had been created
3536  * @modified_objects: (element-type EBookMetaBackendInfo) (nullable):
3537  *    a #GSList of #EBookMetaBackendInfo object infos which had been modified
3538  * @removed_objects: (element-type EBookMetaBackendInfo) (nullable):
3539  *    a #GSList of #EBookMetaBackendInfo object infos which had been removed
3540  * @cancellable: optional #GCancellable object, or %NULL
3541  * @error: return location for a #GError, or %NULL
3542  *
3543  * Processes given changes by updating local cache content accordingly.
3544  * The @meta_backend processes the changes like being online and particularly
3545  * requires to be online to load created and modified objects when needed.
3546  *
3547  * Returns: Whether succeeded.
3548  *
3549  * Since: 3.26
3550  **/
3551 gboolean
e_book_meta_backend_process_changes_sync(EBookMetaBackend * meta_backend,const GSList * created_objects,const GSList * modified_objects,const GSList * removed_objects,GCancellable * cancellable,GError ** error)3552 e_book_meta_backend_process_changes_sync (EBookMetaBackend *meta_backend,
3553 					  const GSList *created_objects,
3554 					  const GSList *modified_objects,
3555 					  const GSList *removed_objects,
3556 					  GCancellable *cancellable,
3557 					  GError **error)
3558 {
3559 	EBookCache *book_cache;
3560 	GHashTable *covered_uids;
3561 	GString *invalid_objects = NULL;
3562 	GSList *link;
3563 	gboolean success = TRUE;
3564 
3565 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3566 
3567 	book_cache = e_book_meta_backend_ref_cache (meta_backend);
3568 	g_return_val_if_fail (book_cache != NULL, FALSE);
3569 
3570 	covered_uids = g_hash_table_new (g_str_hash, g_str_equal);
3571 
3572 	/* Removed objects first */
3573 	for (link = (GSList *) removed_objects; link && success; link = g_slist_next (link)) {
3574 		EBookMetaBackendInfo *nfo = link->data;
3575 
3576 		if (!nfo) {
3577 			g_warn_if_reached ();
3578 			continue;
3579 		}
3580 
3581 		success = ebmb_maybe_remove_from_cache (meta_backend, book_cache, E_CACHE_IS_ONLINE, nfo->uid, 0, cancellable, error);
3582 	}
3583 
3584 	/* Then modified objects */
3585 	for (link = (GSList *) modified_objects; link && success; link = g_slist_next (link)) {
3586 		EBookMetaBackendInfo *nfo = link->data;
3587 		GError *local_error = NULL;
3588 
3589 		if (!nfo || !nfo->uid || !*nfo->uid ||
3590 		    g_hash_table_contains (covered_uids, nfo->uid))
3591 			continue;
3592 
3593 		g_hash_table_insert (covered_uids, nfo->uid, NULL);
3594 
3595 		success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error);
3596 
3597 		/* Do not stop on invalid objects, just notify about them later, and load as many as possible */
3598 		if (!success && g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG)) {
3599 			if (!invalid_objects) {
3600 				invalid_objects = g_string_new (local_error->message);
3601 			} else {
3602 				g_string_append_c (invalid_objects, '\n');
3603 				g_string_append (invalid_objects, local_error->message);
3604 			}
3605 			g_clear_error (&local_error);
3606 			success = TRUE;
3607 		} else if (local_error) {
3608 			g_propagate_error (error, local_error);
3609 		}
3610 	}
3611 
3612 	g_hash_table_remove_all (covered_uids);
3613 
3614 	/* Finally created objects */
3615 	for (link = (GSList *) created_objects; link && success; link = g_slist_next (link)) {
3616 		EBookMetaBackendInfo *nfo = link->data;
3617 		GError *local_error = NULL;
3618 
3619 		if (!nfo || !nfo->uid || !*nfo->uid)
3620 			continue;
3621 
3622 		success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error);
3623 
3624 		/* Do not stop on invalid objects, just notify about them later, and load as many as possible */
3625 		if (!success && g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG)) {
3626 			if (!invalid_objects) {
3627 				invalid_objects = g_string_new (local_error->message);
3628 			} else {
3629 				g_string_append_c (invalid_objects, '\n');
3630 				g_string_append (invalid_objects, local_error->message);
3631 			}
3632 			g_clear_error (&local_error);
3633 			success = TRUE;
3634 		} else if (local_error) {
3635 			g_propagate_error (error, local_error);
3636 		}
3637 	}
3638 
3639 	g_hash_table_destroy (covered_uids);
3640 
3641 	if (invalid_objects) {
3642 		e_book_backend_notify_error (E_BOOK_BACKEND (meta_backend), invalid_objects->str);
3643 
3644 		g_string_free (invalid_objects, TRUE);
3645 	}
3646 
3647 	g_clear_object (&book_cache);
3648 
3649 	return success;
3650 }
3651 
3652 /**
3653  * e_book_meta_backend_connect_sync:
3654  * @meta_backend: an #EBookMetaBackend
3655  * @credentials: (nullable): an #ENamedParameters with previously used credentials, or %NULL
3656  * @out_auth_result: (out): an #ESourceAuthenticationResult with an authentication result
3657  * @out_certificate_pem: (out) (transfer full): a PEM encoded certificate on failure, or %NULL
3658  * @out_certificate_errors: (out): a #GTlsCertificateFlags on failure, or 0
3659  * @cancellable: optional #GCancellable object, or %NULL
3660  * @error: return location for a #GError, or %NULL
3661  *
3662  * This is called always before any operation which requires a connection
3663  * to the remote side. It can fail with an #E_CLIENT_ERROR_REPOSITORY_OFFLINE
3664  * error to indicate that the remote side cannot be currently reached. Other
3665  * errors are propagated to the caller/client side. This method is not called
3666  * when the backend is offline.
3667  *
3668  * The descendant should also call e_book_backend_set_writable() after successful
3669  * connect to the remote side. This value is stored for later use, when being
3670  * opened offline.
3671  *
3672  * The @credentials parameter consists of the previously used credentials.
3673  * It's always %NULL with the first connection attempt. To get the credentials,
3674  * just set the @out_auth_result to %E_SOURCE_AUTHENTICATION_REQUIRED for
3675  * the first time and the function will be called again once the credentials
3676  * are available. See the documentation of #ESourceAuthenticationResult for
3677  * other available results.
3678  *
3679  * The out parameters are passed to e_backend_schedule_credentials_required()
3680  * and are ignored when the descendant returns %TRUE, aka they are used
3681  * only if the connection fails. The @out_certificate_pem and @out_certificate_errors
3682  * should be used together and they can be left untouched if the failure reason was
3683  * not related to certificate. Use @out_auth_result %E_SOURCE_AUTHENTICATION_UNKNOWN
3684  * to indicate other error than @credentials error, otherwise the @error is used
3685  * according to @out_auth_result value.
3686  *
3687  * It is mandatory to implement this virtual method by the descendant.
3688  *
3689  * Returns: Whether succeeded.
3690  *
3691  * Since: 3.26
3692  **/
3693 gboolean
e_book_meta_backend_connect_sync(EBookMetaBackend * meta_backend,const ENamedParameters * credentials,ESourceAuthenticationResult * out_auth_result,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)3694 e_book_meta_backend_connect_sync (EBookMetaBackend *meta_backend,
3695 				  const ENamedParameters *credentials,
3696 				  ESourceAuthenticationResult *out_auth_result,
3697 				  gchar **out_certificate_pem,
3698 				  GTlsCertificateFlags *out_certificate_errors,
3699 				  GCancellable *cancellable,
3700 				  GError **error)
3701 {
3702 	EBookMetaBackendClass *klass;
3703 
3704 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3705 
3706 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
3707 	g_return_val_if_fail (klass != NULL, FALSE);
3708 	g_return_val_if_fail (klass->connect_sync != NULL, FALSE);
3709 
3710 	return klass->connect_sync (meta_backend, credentials, out_auth_result, out_certificate_pem, out_certificate_errors, cancellable, error);
3711 }
3712 
3713 /**
3714  * e_book_meta_backend_disconnect_sync:
3715  * @meta_backend: an #EBookMetaBackend
3716  * @cancellable: optional #GCancellable object, or %NULL
3717  * @error: return location for a #GError, or %NULL
3718  *
3719  * This is called when the backend goes into offline mode or
3720  * when the disconnect is required. The implementation should
3721  * not report any error when it is called and the @meta_backend
3722  * is not connected.
3723  *
3724  * It is mandatory to implement this virtual method by the descendant.
3725  *
3726  * Returns: Whether succeeded.
3727  *
3728  * Since: 3.26
3729  **/
3730 gboolean
e_book_meta_backend_disconnect_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)3731 e_book_meta_backend_disconnect_sync (EBookMetaBackend *meta_backend,
3732 				     GCancellable *cancellable,
3733 				     GError **error)
3734 {
3735 	EBookMetaBackendClass *klass;
3736 
3737 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3738 
3739 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
3740 	g_return_val_if_fail (klass != NULL, FALSE);
3741 	g_return_val_if_fail (klass->disconnect_sync != NULL, FALSE);
3742 
3743 	return klass->disconnect_sync (meta_backend, cancellable, error);
3744 }
3745 
3746 /**
3747  * e_book_meta_backend_get_changes_sync:
3748  * @meta_backend: an #EBookMetaBackend
3749  * @last_sync_tag: (nullable): optional sync tag from the last check
3750  * @is_repeat: set to %TRUE when this is the repeated call
3751  * @out_new_sync_tag: (out) (transfer full): new sync tag to store on success
3752  * @out_repeat: (out): whether to repeat this call again; default is %FALSE
3753  * @out_created_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3754  *    a #GSList of #EBookMetaBackendInfo object infos which had been created since
3755  *    the last check
3756  * @out_modified_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3757  *    a #GSList of #EBookMetaBackendInfo object infos which had been modified since
3758  *    the last check
3759  * @out_removed_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3760  *    a #GSList of #EBookMetaBackendInfo object infos which had been removed since
3761  *    the last check
3762  * @cancellable: optional #GCancellable object, or %NULL
3763  * @error: return location for a #GError, or %NULL
3764  *
3765  * Gathers the changes since the last check which had been done
3766  * on the remote side.
3767  *
3768  * The @last_sync_tag can be used as a tag of the last check. This can be %NULL,
3769  * when there was no previous call or when the descendant doesn't store any
3770  * such tags. The @out_new_sync_tag can be populated with a value to be stored
3771  * and used the next time.
3772  *
3773  * The @out_repeat can be set to %TRUE when the descendant didn't finish
3774  * read of all the changes. In that case the @meta_backend calls this
3775  * function again with the @out_new_sync_tag as the @last_sync_tag, but also
3776  * notifies about the found changes immediately. The @is_repeat is set
3777  * to %TRUE as well in this case, otherwise it's %FALSE.
3778  *
3779  * The descendant can populate also EBookMetaBackendInfo::object of
3780  * the @out_created_objects and @out_modified_objects, if known, in which
3781  * case this will be used instead of loading it with e_book_meta_backend_load_contact_sync().
3782  *
3783  * It is optional to implement this virtual method by the descendant.
3784  * The default implementation calls e_book_meta_backend_list_existing_sync()
3785  * and then compares the list with the current content of the local cache
3786  * and populates the respective lists appropriately.
3787  *
3788  * Each output #GSList should be freed with
3789  * g_slist_free_full (objects, e_book_meta_backend_info_free);
3790  * when no longer needed.
3791  *
3792  * Returns: Whether succeeded.
3793  *
3794  * Since: 3.26
3795  **/
3796 gboolean
e_book_meta_backend_get_changes_sync(EBookMetaBackend * meta_backend,const gchar * last_sync_tag,gboolean is_repeat,gchar ** out_new_sync_tag,gboolean * out_repeat,GSList ** out_created_objects,GSList ** out_modified_objects,GSList ** out_removed_objects,GCancellable * cancellable,GError ** error)3797 e_book_meta_backend_get_changes_sync (EBookMetaBackend *meta_backend,
3798 				      const gchar *last_sync_tag,
3799 				      gboolean is_repeat,
3800 				      gchar **out_new_sync_tag,
3801 				      gboolean *out_repeat,
3802 				      GSList **out_created_objects,
3803 				      GSList **out_modified_objects,
3804 				      GSList **out_removed_objects,
3805 				      GCancellable *cancellable,
3806 				      GError **error)
3807 {
3808 	EBookMetaBackendClass *klass;
3809 	gint repeat_count = 0;
3810 	gboolean success = FALSE;
3811 	GError *local_error = NULL;
3812 
3813 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3814 	g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
3815 	g_return_val_if_fail (out_repeat != NULL, FALSE);
3816 	g_return_val_if_fail (out_created_objects != NULL, FALSE);
3817 	g_return_val_if_fail (out_created_objects != NULL, FALSE);
3818 	g_return_val_if_fail (out_modified_objects != NULL, FALSE);
3819 	g_return_val_if_fail (out_removed_objects != NULL, FALSE);
3820 
3821 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
3822 	g_return_val_if_fail (klass != NULL, FALSE);
3823 	g_return_val_if_fail (klass->get_changes_sync != NULL, FALSE);
3824 
3825 	while (!success && repeat_count <= MAX_REPEAT_COUNT) {
3826 		guint wait_credentials_stamp;
3827 
3828 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
3829 		wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
3830 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
3831 
3832 		g_clear_error (&local_error);
3833 		repeat_count++;
3834 
3835 		success = klass->get_changes_sync (meta_backend,
3836 			last_sync_tag,
3837 			is_repeat,
3838 			out_new_sync_tag,
3839 			out_repeat,
3840 			out_created_objects,
3841 			out_modified_objects,
3842 			out_removed_objects,
3843 			cancellable,
3844 			&local_error);
3845 
3846 		if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable))
3847 			break;
3848 	}
3849 
3850 	if (local_error)
3851 		g_propagate_error (error, local_error);
3852 
3853 	return success;
3854 }
3855 
3856 /**
3857  * e_book_meta_backend_list_existing_sync:
3858  * @meta_backend: an #EBookMetaBackend
3859  * @out_new_sync_tag: (out) (transfer full): optional return location for a new sync tag
3860  * @out_existing_objects: (out) (element-type EBookMetaBackendInfo) (transfer full):
3861  *    a #GSList of #EBookMetaBackendInfo object infos which are stored on the remote side
3862  * @cancellable: optional #GCancellable object, or %NULL
3863  * @error: return location for a #GError, or %NULL
3864  *
3865  * Used to get list of all existing objects on the remote side. The descendant
3866  * can optionally provide @out_new_sync_tag, which will be stored on success, if
3867  * not %NULL. The descendant can populate also EBookMetaBackendInfo::object of
3868  * the @out_existing_objects, if known, in which case this will be used instead
3869  * of loading it with e_book_meta_backend_load_contact_sync().
3870  *
3871  * It is mandatory to implement this virtual method by the descendant, unless
3872  * it implements its own #EBookMetaBackendClass.get_changes_sync().
3873  *
3874  * The @out_existing_objects #GSList should be freed with
3875  * g_slist_free_full (objects, e_book_meta_backend_info_free);
3876  * when no longer needed.
3877  *
3878  * Returns: Whether succeeded.
3879  *
3880  * Since: 3.26
3881  **/
3882 gboolean
e_book_meta_backend_list_existing_sync(EBookMetaBackend * meta_backend,gchar ** out_new_sync_tag,GSList ** out_existing_objects,GCancellable * cancellable,GError ** error)3883 e_book_meta_backend_list_existing_sync (EBookMetaBackend *meta_backend,
3884 				        gchar **out_new_sync_tag,
3885 				        GSList **out_existing_objects,
3886 				        GCancellable *cancellable,
3887 				        GError **error)
3888 {
3889 	EBookMetaBackendClass *klass;
3890 	gint repeat_count = 0;
3891 	gboolean success = FALSE;
3892 	GError *local_error = NULL;
3893 
3894 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3895 	g_return_val_if_fail (out_existing_objects != NULL, FALSE);
3896 
3897 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
3898 	g_return_val_if_fail (klass != NULL, FALSE);
3899 	g_return_val_if_fail (klass->list_existing_sync != NULL, FALSE);
3900 
3901 
3902 	while (!success && repeat_count <= MAX_REPEAT_COUNT) {
3903 		guint wait_credentials_stamp;
3904 
3905 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
3906 		wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
3907 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
3908 
3909 		g_clear_error (&local_error);
3910 		repeat_count++;
3911 
3912 		success = klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, cancellable, &local_error);
3913 
3914 		if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable))
3915 			break;
3916 	}
3917 
3918 	if (local_error)
3919 		g_propagate_error (error, local_error);
3920 
3921 	return success;
3922 }
3923 
3924 /**
3925  * e_book_meta_backend_load_contact_sync:
3926  * @meta_backend: an #EBookMetaBackend
3927  * @uid: a contact UID
3928  * @extra: (nullable): optional extra data stored with the contact, or %NULL
3929  * @out_contact: (out) (transfer full): a loaded contact, as an #EContact
3930  * @out_extra: (out) (transfer full): an extra data to store to #EBookCache with this contact
3931  * @cancellable: optional #GCancellable object, or %NULL
3932  * @error: return location for a #GError, or %NULL
3933  *
3934  * Loads a contact from the remote side.
3935  *
3936  * It is mandatory to implement this virtual method by the descendant.
3937  *
3938  * The returned @out_contact should be freed with g_object_unref(),
3939  * when no longer needed.
3940  *
3941  * The returned @out_extra should be freed with g_free(), when no longer
3942  * needed.
3943  *
3944  * Returns: Whether succeeded.
3945  *
3946  * Since: 3.26
3947  **/
3948 gboolean
e_book_meta_backend_load_contact_sync(EBookMetaBackend * meta_backend,const gchar * uid,const gchar * extra,EContact ** out_contact,gchar ** out_extra,GCancellable * cancellable,GError ** error)3949 e_book_meta_backend_load_contact_sync (EBookMetaBackend *meta_backend,
3950 				       const gchar *uid,
3951 				       const gchar *extra,
3952 				       EContact **out_contact,
3953 				       gchar **out_extra,
3954 				       GCancellable *cancellable,
3955 				       GError **error)
3956 {
3957 	EBookMetaBackendClass *klass;
3958 	gint repeat_count = 0;
3959 	gboolean success = FALSE;
3960 	GError *local_error = NULL;
3961 
3962 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
3963 	g_return_val_if_fail (uid != NULL, FALSE);
3964 	g_return_val_if_fail (out_contact != NULL, FALSE);
3965 	g_return_val_if_fail (out_extra != NULL, FALSE);
3966 
3967 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
3968 	g_return_val_if_fail (klass != NULL, FALSE);
3969 	g_return_val_if_fail (klass->load_contact_sync != NULL, FALSE);
3970 
3971 
3972 	while (!success && repeat_count <= MAX_REPEAT_COUNT) {
3973 		guint wait_credentials_stamp;
3974 
3975 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
3976 		wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
3977 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
3978 
3979 		g_clear_error (&local_error);
3980 		repeat_count++;
3981 
3982 		success = klass->load_contact_sync (meta_backend, uid, extra, out_contact, out_extra, cancellable, &local_error);
3983 
3984 		if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable))
3985 			break;
3986 	}
3987 
3988 	if (local_error)
3989 		g_propagate_error (error, local_error);
3990 
3991 	return success;
3992 }
3993 
3994 /**
3995  * e_book_meta_backend_save_contact_sync:
3996  * @meta_backend: an #EBookMetaBackend
3997  * @overwrite_existing: %TRUE when can overwrite existing contacts, %FALSE otherwise
3998  * @conflict_resolution: one of #EConflictResolution, what to do on conflicts
3999  * @contact: an #EContact to save
4000  * @extra: (nullable): extra data saved with the contacts in an #EBookCache
4001  * @opflags: bit-or of EBookOperationFlags
4002  * @out_new_uid: (out) (transfer full): return location for the UID of the saved contact
4003  * @out_new_extra: (out) (transfer full): return location for the extra data to store with the contact
4004  * @cancellable: optional #GCancellable object, or %NULL
4005  * @error: return location for a #GError, or %NULL
4006  *
4007  * Saves one contact into the remote side.  When the @overwrite_existing is %TRUE, then
4008  * the descendant can overwrite an object with the same UID on the remote side
4009  * (usually used for modify). The @conflict_resolution defines what to do when
4010  * the remote side had made any changes to the object since the last update.
4011  *
4012  * The @contact has already converted locally stored photos and logos
4013  * into inline variants, thus it's not needed to call
4014  * e_book_meta_backend_inline_local_photos_sync() by the descendant.
4015  *
4016  * The @out_new_uid can be populated with a UID of the saved contact as the server
4017  * assigned it to it. This UID, if set, is loaded from the remote side afterwards,
4018  * also to see whether any changes had been made to the contact by the remote side.
4019  *
4020  * The @out_new_extra can be populated with a new extra data to save with the contact.
4021  * Left it %NULL, to keep the same value as the @extra.
4022  *
4023  * The descendant can use an #E_CLIENT_ERROR_OUT_OF_SYNC error to indicate that
4024  * the save failed due to made changes on the remote side, and let the @meta_backend
4025  * resolve this conflict based on the @conflict_resolution on its own.
4026  * The #E_CLIENT_ERROR_OUT_OF_SYNC error should not be used when the descendant
4027  * is able to resolve the conflicts itself.
4028  *
4029  * It is mandatory to implement this virtual method by the writable descendant.
4030  *
4031  * Returns: Whether succeeded.
4032  *
4033  * Since: 3.26
4034  **/
4035 gboolean
e_book_meta_backend_save_contact_sync(EBookMetaBackend * meta_backend,gboolean overwrite_existing,EConflictResolution conflict_resolution,EContact * contact,const gchar * extra,guint32 opflags,gchar ** out_new_uid,gchar ** out_new_extra,GCancellable * cancellable,GError ** error)4036 e_book_meta_backend_save_contact_sync (EBookMetaBackend *meta_backend,
4037 				       gboolean overwrite_existing,
4038 				       EConflictResolution conflict_resolution,
4039 				       /* const */ EContact *contact,
4040 				       const gchar *extra,
4041 				       guint32 opflags,
4042 				       gchar **out_new_uid,
4043 				       gchar **out_new_extra,
4044 				       GCancellable *cancellable,
4045 				       GError **error)
4046 {
4047 	EBookMetaBackendClass *klass;
4048 	gint repeat_count = 0;
4049 	gboolean success = FALSE;
4050 	GError *local_error = NULL;
4051 
4052 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4053 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
4054 	g_return_val_if_fail (out_new_uid != NULL, FALSE);
4055 	g_return_val_if_fail (out_new_extra != NULL, FALSE);
4056 
4057 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4058 	g_return_val_if_fail (klass != NULL, FALSE);
4059 
4060 	if (!klass->save_contact_sync) {
4061 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_NOT_SUPPORTED, NULL));
4062 		return FALSE;
4063 	}
4064 
4065 
4066 	while (!success && repeat_count <= MAX_REPEAT_COUNT) {
4067 		guint wait_credentials_stamp;
4068 
4069 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
4070 		wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
4071 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
4072 
4073 		g_clear_error (&local_error);
4074 		repeat_count++;
4075 
4076 		success = klass->save_contact_sync (meta_backend,
4077 			overwrite_existing,
4078 			conflict_resolution,
4079 			contact,
4080 			extra,
4081 			opflags,
4082 			out_new_uid,
4083 			out_new_extra,
4084 			cancellable,
4085 			&local_error);
4086 
4087 		if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable))
4088 			break;
4089 	}
4090 
4091 	if (local_error)
4092 		g_propagate_error (error, local_error);
4093 
4094 	return success;
4095 }
4096 
4097 /**
4098  * e_book_meta_backend_remove_contact_sync:
4099  * @meta_backend: an #EBookMetaBackend
4100  * @conflict_resolution: an #EConflictResolution to use
4101  * @uid: a contact UID
4102  * @extra: (nullable): extra data being saved with the contact in the local cache, or %NULL
4103  * @object: (nullable): corresponding vCard object, as stored in the local cache, or %NULL
4104  * @opflags: bit-or of #EBookOperationFlags
4105  * @cancellable: optional #GCancellable object, or %NULL
4106  * @error: return location for a #GError, or %NULL
4107  *
4108  * Removes a contact from the remote side. The @object is not %NULL when
4109  * it's removing locally deleted object in offline mode. Being it %NULL,
4110  * the descendant can obtain the object from the #EBookCache.
4111  *
4112  * It is mandatory to implement this virtual method by the writable descendant.
4113  *
4114  * Returns: Whether succeeded.
4115  *
4116  * Since: 3.26
4117  **/
4118 gboolean
e_book_meta_backend_remove_contact_sync(EBookMetaBackend * meta_backend,EConflictResolution conflict_resolution,const gchar * uid,const gchar * extra,const gchar * object,guint32 opflags,GCancellable * cancellable,GError ** error)4119 e_book_meta_backend_remove_contact_sync (EBookMetaBackend *meta_backend,
4120 					 EConflictResolution conflict_resolution,
4121 					 const gchar *uid,
4122 					 const gchar *extra,
4123 					 const gchar *object,
4124 					 guint32 opflags,
4125 					 GCancellable *cancellable,
4126 					 GError **error)
4127 {
4128 	EBookMetaBackendClass *klass;
4129 	gint repeat_count = 0;
4130 	gboolean success = FALSE;
4131 	GError *local_error = NULL;
4132 
4133 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4134 	g_return_val_if_fail (uid != NULL, FALSE);
4135 
4136 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4137 	g_return_val_if_fail (klass != NULL, FALSE);
4138 
4139 	if (!klass->remove_contact_sync) {
4140 		g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_NOT_SUPPORTED, NULL));
4141 		return FALSE;
4142 	}
4143 
4144 
4145 	while (!success && repeat_count <= MAX_REPEAT_COUNT) {
4146 		guint wait_credentials_stamp;
4147 
4148 		g_mutex_lock (&meta_backend->priv->wait_credentials_lock);
4149 		wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp;
4150 		g_mutex_unlock (&meta_backend->priv->wait_credentials_lock);
4151 
4152 		g_clear_error (&local_error);
4153 		repeat_count++;
4154 
4155 		success = klass->remove_contact_sync (meta_backend, conflict_resolution, uid, extra, object, opflags, cancellable, &local_error);
4156 
4157 		if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable))
4158 			break;
4159 	}
4160 
4161 	if (local_error)
4162 		g_propagate_error (error, local_error);
4163 
4164 	return success;
4165 }
4166 
4167 /**
4168  * e_book_meta_backend_search_sync:
4169  * @meta_backend: an #EBookMetaBackend
4170  * @expr: (nullable): a search expression, or %NULL
4171  * @meta_contact: %TRUE, when return #EContact filled with UID and REV only, %FALSE to return full contacts
4172  * @out_contacts: (out) (transfer full) (element-type EContact): return location for the found contacts as #EContact
4173  * @cancellable: optional #GCancellable object, or %NULL
4174  * @error: return location for a #GError, or %NULL
4175  *
4176  * Searches @meta_backend with given expression @expr and returns
4177  * found contacts as a #GSList of #EContact @out_contacts.
4178  * Free the returned @out_contacts with g_slist_free_full (contacts, g_object_unref);
4179  * when no longer needed.
4180  * When the @expr is %NULL, all objects are returned. To get
4181  * UID-s instead, call e_book_meta_backend_search_uids_sync().
4182  *
4183  * It is optional to implement this virtual method by the descendant.
4184  * The default implementation searches @meta_backend's cache. It's also
4185  * not required to be online for searching, thus @meta_backend doesn't
4186  * ensure it.
4187  *
4188  * Returns: Whether succeeded.
4189  *
4190  * Since: 3.26
4191  **/
4192 gboolean
e_book_meta_backend_search_sync(EBookMetaBackend * meta_backend,const gchar * expr,gboolean meta_contact,GSList ** out_contacts,GCancellable * cancellable,GError ** error)4193 e_book_meta_backend_search_sync (EBookMetaBackend *meta_backend,
4194 				 const gchar *expr,
4195 				 gboolean meta_contact,
4196 				 GSList **out_contacts,
4197 				 GCancellable *cancellable,
4198 				 GError **error)
4199 {
4200 	EBookMetaBackendClass *klass;
4201 
4202 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4203 	g_return_val_if_fail (out_contacts != NULL, FALSE);
4204 
4205 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4206 	g_return_val_if_fail (klass != NULL, FALSE);
4207 	g_return_val_if_fail (klass->search_sync != NULL, FALSE);
4208 
4209 	return klass->search_sync (meta_backend, expr, meta_contact, out_contacts, cancellable, error);
4210 }
4211 
4212 /**
4213  * e_book_meta_backend_search_uids_sync:
4214  * @meta_backend: an #EBookMetaBackend
4215  * @expr: (nullable): a search expression, or %NULL
4216  * @out_uids: (out) (transfer full) (element-type utf8): return location for the found contact UID-s
4217  * @cancellable: optional #GCancellable object, or %NULL
4218  * @error: return location for a #GError, or %NULL
4219  *
4220  * Searches @meta_backend with given expression @expr and returns
4221  * found contact UID-s as a #GSList @out_contacts.
4222  * Free the returned @out_uids with g_slist_free_full (uids, g_free);
4223  * when no longer needed.
4224  * When the @expr is %NULL, all UID-s are returned. To get #EContact(s)
4225  * instead, call e_book_meta_backend_search_sync().
4226  *
4227  * It is optional to implement this virtual method by the descendant.
4228  * The default implementation searches @meta_backend's cache. It's also
4229  * not required to be online for searching, thus @meta_backend doesn't
4230  * ensure it.
4231  *
4232  * Returns: Whether succeeded.
4233  *
4234  * Since: 3.26
4235  **/
4236 gboolean
e_book_meta_backend_search_uids_sync(EBookMetaBackend * meta_backend,const gchar * expr,GSList ** out_uids,GCancellable * cancellable,GError ** error)4237 e_book_meta_backend_search_uids_sync (EBookMetaBackend *meta_backend,
4238 				      const gchar *expr,
4239 				      GSList **out_uids,
4240 				      GCancellable *cancellable,
4241 				      GError **error)
4242 {
4243 	EBookMetaBackendClass *klass;
4244 
4245 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4246 	g_return_val_if_fail (out_uids != NULL, FALSE);
4247 
4248 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4249 	g_return_val_if_fail (klass != NULL, FALSE);
4250 	g_return_val_if_fail (klass->search_uids_sync != NULL, FALSE);
4251 
4252 	return klass->search_uids_sync (meta_backend, expr, out_uids, cancellable, error);
4253 }
4254 
4255 /**
4256  * e_book_meta_backend_requires_reconnect:
4257  * @meta_backend: an #EBookMetaBackend
4258  *
4259  * Determines, whether current source content requires reconnect of the backend.
4260  *
4261  * It is optional to implement this virtual method by the descendant. The default
4262  * implementation compares %E_SOURCE_EXTENSION_AUTHENTICATION and
4263  * %E_SOURCE_EXTENSION_WEBDAV_BACKEND, if existing in the source,
4264  * with the values after the last successful connect and returns
4265  * %TRUE when they changed. It always return %TRUE when there was
4266  * no successful connect done yet.
4267  *
4268  * Returns: %TRUE, when reconnect is required, %FALSE otherwise.
4269  *
4270  * Since: 3.26
4271  **/
4272 gboolean
e_book_meta_backend_requires_reconnect(EBookMetaBackend * meta_backend)4273 e_book_meta_backend_requires_reconnect (EBookMetaBackend *meta_backend)
4274 {
4275 	EBookMetaBackendClass *klass;
4276 
4277 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4278 
4279 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4280 	g_return_val_if_fail (klass != NULL, FALSE);
4281 	g_return_val_if_fail (klass->requires_reconnect != NULL, FALSE);
4282 
4283 	return klass->requires_reconnect (meta_backend);
4284 }
4285 
4286 /**
4287  * e_book_meta_backend_get_ssl_error_details:
4288  * @meta_backend: an #EBookMetaBackend
4289  * @out_certificate_pem: (out): SSL certificate encoded in PEM format
4290  * @out_certificate_errors: (out): bit-or of #GTlsCertificateFlags claiming the certificate errors
4291  *
4292  * It is optional to implement this virtual method by the descendants.
4293  * It is used to receive SSL error details when any online operation
4294  * returns E_CLIENT_ERROR, E_CLIENT_ERROR_TLS_NOT_AVAILABLE error.
4295  *
4296  * Returns: %TRUE, when the SSL error details had been available and
4297  *    the out parameters populated, %FALSE otherwise.
4298  *
4299  * Since: 3.28
4300  **/
4301 gboolean
e_book_meta_backend_get_ssl_error_details(EBookMetaBackend * meta_backend,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors)4302 e_book_meta_backend_get_ssl_error_details (EBookMetaBackend *meta_backend,
4303 					   gchar **out_certificate_pem,
4304 					   GTlsCertificateFlags *out_certificate_errors)
4305 {
4306 	EBookMetaBackendClass *klass;
4307 
4308 	g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE);
4309 
4310 	klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend);
4311 	g_return_val_if_fail (klass != NULL, FALSE);
4312 	g_return_val_if_fail (klass->get_ssl_error_details != NULL, FALSE);
4313 
4314 	return klass->get_ssl_error_details (meta_backend, out_certificate_pem, out_certificate_errors);
4315 }
4316