/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2017 Red Hat, Inc. (www.redhat.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ /** * SECTION: e-book-meta-backend * @include: libedata-book/libedata-book.h * @short_description: An #EBookBackend descendant for book backends * * The #EBookMetaBackend is an abstract #EBookBackend descendant which * aims to implement all evolution-data-server internals for the backend * itself and lefts the backend do as minimum work as possible, like * loading and saving contacts, listing available contacts and so on, * thus the backend implementation can focus on things like converting * (possibly) remote data into vCard objects and back. * * As the #EBookMetaBackend uses an #EBookCache, the offline support * is provided by default. * * The structure is thread safe. **/ #include "evolution-data-server-config.h" #include #include #include #include "e-book-backend-sexp.h" #include "e-book-backend.h" #include "e-data-book-cursor-cache.h" #include "e-data-book-factory.h" #include "e-book-meta-backend.h" #define EBMB_KEY_SYNC_TAG "ebmb::sync-tag" #define EBMB_KEY_EVER_CONNECTED "ebmb::ever-connected" #define EBMB_KEY_CONNECTED_WRITABLE "ebmb::connected-writable" #define LOCAL_PREFIX "file://" /* How many times can repeat an operation when credentials fail. */ #define MAX_REPEAT_COUNT 3 /* How long to wait for credentials, in seconds, during the operation repeat cycle */ #define MAX_WAIT_FOR_CREDENTIALS_SECS 60 struct _EBookMetaBackendPrivate { GMutex connect_lock; GMutex property_lock; GMutex wait_credentials_lock; GCond wait_credentials_cond; guint wait_credentials_stamp; GError *create_cache_error; EBookCache *cache; ENamedParameters *last_credentials; GHashTable *view_cancellables; GCancellable *refresh_cancellable; /* Set when refreshing the content */ GCancellable *source_changed_cancellable; /* Set when processing source changed signal */ GCancellable *go_offline_cancellable; /* Set when going offline */ gboolean current_online_state; /* The only state of the internal structures; used to detect false notifications on EBackend::online */ gulong source_changed_id; gulong notify_online_id; gulong revision_changed_id; gulong categories_changed_id; guint refresh_timeout_id; gboolean refresh_after_authenticate; gint ever_connected; gint connected_writable; gint64 last_refresh_time; /* The time when the last refresh was called */ /* Last successful connect data, for some extensions */ guint16 authentication_port; gchar *authentication_host; gchar *authentication_user; gchar *authentication_method; gchar *authentication_proxy_uid; gchar *authentication_credential_name; GUri *webdav_uri; GSList *cursors; }; enum { PROP_0, PROP_CACHE }; enum { REFRESH_COMPLETED, SOURCE_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (EBookMetaBackend, e_book_meta_backend, E_TYPE_BOOK_BACKEND_SYNC) G_DEFINE_BOXED_TYPE (EBookMetaBackendInfo, e_book_meta_backend_info, e_book_meta_backend_info_copy, e_book_meta_backend_info_free) static void ebmb_schedule_source_changed (EBookMetaBackend *meta_backend); static void ebmb_schedule_go_offline (EBookMetaBackend *meta_backend); static gboolean ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend, EBookCache *book_cache, gboolean overwrite_existing, EConflictResolution conflict_resolution, /* const */ 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); static gboolean ebmb_is_power_saver_enabled (void) { #ifdef HAVE_GPOWERPROFILEMONITOR GSettings *settings; gboolean enabled = FALSE; settings = g_settings_new ("org.gnome.evolution-data-server"); if (g_settings_get_boolean (settings, "limit-operations-in-power-saver-mode")) { GPowerProfileMonitor *power_monitor; power_monitor = g_power_profile_monitor_dup_default (); enabled = power_monitor && g_power_profile_monitor_get_power_saver_enabled (power_monitor); g_clear_object (&power_monitor); } g_clear_object (&settings); return enabled; #else return FALSE; #endif } /** * e_book_meta_backend_info_new: * @uid: a contact UID; cannot be %NULL * @revision: (nullable): the contact revision; can be %NULL * @object: (nullable): the contact object as a vCard string; can be %NULL * @extra: (nullable): extra backend-specific data; can be %NULL * * Creates a new #EBookMetaBackendInfo prefilled with the given values. * * Returns: (transfer full): A new #EBookMetaBackendInfo. Free it with * e_book_meta_backend_info_free(), when no longer needed. * * Since: 3.26 **/ EBookMetaBackendInfo * e_book_meta_backend_info_new (const gchar *uid, const gchar *revision, const gchar *object, const gchar *extra) { EBookMetaBackendInfo *info; g_return_val_if_fail (uid != NULL, NULL); info = g_slice_new0 (EBookMetaBackendInfo); info->uid = g_strdup (uid); info->revision = g_strdup (revision); info->object = g_strdup (object); info->extra = g_strdup (extra); return info; } /** * e_book_meta_backend_info_copy: * @src: (nullable): a source EBookMetaBackendInfo to copy, or %NULL * * Returns: (transfer full) (nullable): Copy of the given @src. Free it with * e_book_meta_backend_info_free() when no longer needed. * If the @src is %NULL, then returns %NULL as well. * * Since: 3.26 **/ EBookMetaBackendInfo * e_book_meta_backend_info_copy (const EBookMetaBackendInfo *src) { if (!src) return NULL; return e_book_meta_backend_info_new (src->uid, src->revision, src->object, src->extra); } /** * e_book_meta_backend_info_free: * @ptr: (nullable): an #EBookMetaBackendInfo * * Frees the @ptr structure, previously allocated with e_book_meta_backend_info_new() * or e_book_meta_backend_info_copy(). * * Since: 3.26 **/ void e_book_meta_backend_info_free (gpointer ptr) { EBookMetaBackendInfo *info = ptr; if (info) { g_free (info->uid); g_free (info->revision); g_free (info->object); g_free (info->extra); g_slice_free (EBookMetaBackendInfo, info); } } /* Unref returned cancellable with g_object_unref(), when done with it */ static GCancellable * ebmb_create_view_cancellable (EBookMetaBackend *meta_backend, EDataBookView *view) { GCancellable *cancellable; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL); g_mutex_lock (&meta_backend->priv->property_lock); cancellable = g_cancellable_new (); g_hash_table_insert (meta_backend->priv->view_cancellables, view, g_object_ref (cancellable)); g_mutex_unlock (&meta_backend->priv->property_lock); return cancellable; } static GCancellable * ebmb_steal_view_cancellable (EBookMetaBackend *meta_backend, EDataBookView *view) { GCancellable *cancellable; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (view), NULL); g_mutex_lock (&meta_backend->priv->property_lock); cancellable = g_hash_table_lookup (meta_backend->priv->view_cancellables, view); if (cancellable) { g_object_ref (cancellable); g_hash_table_remove (meta_backend->priv->view_cancellables, view); } g_mutex_unlock (&meta_backend->priv->property_lock); return cancellable; } static void ebmb_update_connection_values (EBookMetaBackend *meta_backend) { ESource *source; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); source = e_backend_get_source (E_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->property_lock); meta_backend->priv->authentication_port = 0; g_clear_pointer (&meta_backend->priv->authentication_host, g_free); g_clear_pointer (&meta_backend->priv->authentication_user, g_free); g_clear_pointer (&meta_backend->priv->authentication_method, g_free); g_clear_pointer (&meta_backend->priv->authentication_proxy_uid, g_free); g_clear_pointer (&meta_backend->priv->authentication_credential_name, g_free); g_clear_pointer (&meta_backend->priv->webdav_uri, g_uri_unref); if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *auth_extension; auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); meta_backend->priv->authentication_port = e_source_authentication_get_port (auth_extension); meta_backend->priv->authentication_host = e_source_authentication_dup_host (auth_extension); meta_backend->priv->authentication_user = e_source_authentication_dup_user (auth_extension); meta_backend->priv->authentication_method = e_source_authentication_dup_method (auth_extension); meta_backend->priv->authentication_proxy_uid = e_source_authentication_dup_proxy_uid (auth_extension); meta_backend->priv->authentication_credential_name = e_source_authentication_dup_credential_name (auth_extension); } if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) { ESourceWebdav *webdav_extension; webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); meta_backend->priv->webdav_uri = e_source_webdav_dup_uri (webdav_extension); } g_mutex_unlock (&meta_backend->priv->property_lock); e_book_meta_backend_set_ever_connected (meta_backend, TRUE); e_book_meta_backend_set_connected_writable (meta_backend, e_book_backend_get_writable (E_BOOK_BACKEND (meta_backend))); } 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) { GHashTable *locally_cached = user_data; g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (locally_cached != NULL, FALSE); if (offline_state == E_OFFLINE_STATE_SYNCED) { g_hash_table_insert (locally_cached, g_strdup (uid), g_strdup (revision)); } return TRUE; } 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) { EBackend *backend; GSList *existing_objects = NULL; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_created_objects, FALSE); g_return_val_if_fail (out_modified_objects, FALSE); g_return_val_if_fail (out_removed_objects, FALSE); *out_created_objects = NULL; *out_modified_objects = NULL; *out_removed_objects = NULL; backend = E_BACKEND (meta_backend); if (!e_backend_get_online (backend) && !e_backend_is_destination_reachable (backend, cancellable, NULL)) return TRUE; else e_backend_set_online (backend, TRUE); if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error) || !e_book_meta_backend_list_existing_sync (meta_backend, out_new_sync_tag, &existing_objects, cancellable, error)) { return FALSE; } success = e_book_meta_backend_split_changes_sync (meta_backend, existing_objects, out_created_objects, out_modified_objects, out_removed_objects, cancellable, error); g_slist_free_full (existing_objects, e_book_meta_backend_info_free); return success; } static gboolean ebmb_search_sync (EBookMetaBackend *meta_backend, const gchar *expr, gboolean meta_contact, GSList **out_contacts, GCancellable *cancellable, GError **error) { EBookCache *book_cache; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_contacts != NULL, FALSE); *out_contacts = NULL; book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); success = e_book_cache_search (book_cache, expr, meta_contact, out_contacts, cancellable, error); if (success) { GSList *link; for (link = *out_contacts; link; link = g_slist_next (link)) { EBookCacheSearchData *search_data = link->data; EContact *contact = NULL; if (search_data) { contact = e_contact_new_from_vcard_with_uid (search_data->vcard, search_data->uid); e_book_cache_search_data_free (search_data); } link->data = contact; } } g_object_unref (book_cache); return success; } static gboolean ebmb_search_uids_sync (EBookMetaBackend *meta_backend, const gchar *expr, GSList **out_uids, GCancellable *cancellable, GError **error) { EBookCache *book_cache; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_uids != NULL, FALSE); *out_uids = NULL; book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); success = e_book_cache_search_uids (book_cache, expr, out_uids, cancellable, error); g_object_unref (book_cache); return success; } static gboolean ebmb_requires_reconnect (EBookMetaBackend *meta_backend) { ESource *source; gboolean requires = FALSE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); source = e_backend_get_source (E_BACKEND (meta_backend)); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_mutex_lock (&meta_backend->priv->property_lock); if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *auth_extension; auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); e_source_extension_property_lock (E_SOURCE_EXTENSION (auth_extension)); requires = meta_backend->priv->authentication_port != e_source_authentication_get_port (auth_extension) || g_strcmp0 (meta_backend->priv->authentication_host, e_source_authentication_get_host (auth_extension)) != 0 || g_strcmp0 (meta_backend->priv->authentication_user, e_source_authentication_get_user (auth_extension)) != 0 || g_strcmp0 (meta_backend->priv->authentication_method, e_source_authentication_get_method (auth_extension)) != 0 || g_strcmp0 (meta_backend->priv->authentication_proxy_uid, e_source_authentication_get_proxy_uid (auth_extension)) != 0 || g_strcmp0 (meta_backend->priv->authentication_credential_name, e_source_authentication_get_credential_name (auth_extension)) != 0; e_source_extension_property_unlock (E_SOURCE_EXTENSION (auth_extension)); } if (!requires && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) { ESourceWebdav *webdav_extension; GUri *g_uri; webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); g_uri = e_source_webdav_dup_uri (webdav_extension); requires = (!meta_backend->priv->webdav_uri && g_uri) || (g_uri && meta_backend->priv->webdav_uri && !soup_uri_equal (meta_backend->priv->webdav_uri, g_uri)); if (g_uri) g_uri_unref (g_uri); } g_mutex_unlock (&meta_backend->priv->property_lock); return requires; } static gboolean ebmb_get_ssl_error_details (EBookMetaBackend *meta_backend, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors) { return FALSE; } static GSList * /* gchar * */ ebmb_gather_photos_local_filenames (EBookMetaBackend *meta_backend, EContact *contact) { EBookCache *book_cache; GList *attributes, *link; GSList *filenames = NULL; gchar *cache_path; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); g_return_val_if_fail (E_IS_CONTACT (contact), NULL); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, NULL); cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache))); g_object_unref (book_cache); attributes = e_vcard_get_attributes (E_VCARD (contact)); for (link = attributes; link; link = g_list_next (link)) { EVCardAttribute *attr = link->data; const gchar *attr_name; GList *values; attr_name = e_vcard_attribute_get_name (attr); if (!attr_name || ( g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 && g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) { continue; } values = e_vcard_attribute_get_param (attr, EVC_VALUE); if (values && g_ascii_strcasecmp (values->data, "uri") == 0) { gchar *url; url = e_vcard_attribute_get_value (attr); if (url && g_str_has_prefix (url, LOCAL_PREFIX)) { gchar *filename; filename = g_filename_from_uri (url, NULL, NULL); if (filename && g_str_has_prefix (filename, cache_path)) filenames = g_slist_prepend (filenames, filename); else g_free (filename); } g_free (url); } } g_free (cache_path); return filenames; } static void ebmb_start_view_thread_func (EBookBackend *book_backend, gpointer user_data, GCancellable *cancellable, GError **error) { EDataBookView *view = user_data; EBookBackendSExp *sexp; GSList *contacts = NULL; const gchar *expr = NULL; gboolean meta_contact = FALSE; GHashTable *fields_of_interest; GError *local_error = NULL; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); g_return_if_fail (E_IS_DATA_BOOK_VIEW (view)); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return; /* Fill the view with known (locally stored) contacts satisfying the expression */ sexp = e_data_book_view_get_sexp (view); if (sexp) expr = e_book_backend_sexp_text (sexp); fields_of_interest = e_data_book_view_get_fields_of_interest (view); if (fields_of_interest && g_hash_table_size (fields_of_interest) == 2) { GHashTableIter iter; gpointer key, value; meta_contact = TRUE; g_hash_table_iter_init (&iter, fields_of_interest); while (g_hash_table_iter_next (&iter, &key, &value)) { const gchar *field_name = key; EContactField field = e_contact_field_id (field_name); if (field != E_CONTACT_UID && field != E_CONTACT_REV) { meta_contact = FALSE; break; } } } if (e_book_meta_backend_search_sync (E_BOOK_META_BACKEND (book_backend), expr, meta_contact, &contacts, cancellable, &local_error) && contacts) { if (!g_cancellable_is_cancelled (cancellable)) { GSList *link; for (link = contacts; link; link = g_slist_next (link)) { EContact *contact = link->data; gchar *vcard; if (!contact) continue; vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30); e_data_book_view_notify_update_prefiltered_vcard (view, e_contact_get_const (contact, E_CONTACT_UID), vcard); g_free (vcard); } } g_slist_free_full (contacts, g_object_unref); } e_data_book_view_notify_complete (view, local_error); g_clear_error (&local_error); } static gboolean ebmb_upload_local_changes_sync (EBookMetaBackend *meta_backend, EBookCache *book_cache, EConflictResolution conflict_resolution, GCancellable *cancellable, GError **error) { GSList *offline_changes, *link; GHashTable *covered_uids; ECache *cache; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE); cache = E_CACHE (book_cache); covered_uids = g_hash_table_new (g_str_hash, g_str_equal); offline_changes = e_cache_get_offline_changes (cache, cancellable, error); for (link = offline_changes; link && success; link = g_slist_next (link)) { ECacheOfflineChange *change = link->data; gchar *extra = NULL; guint32 opflags = 0; success = !g_cancellable_set_error_if_cancelled (cancellable, error); if (!success) break; if (!change || g_hash_table_contains (covered_uids, change->uid)) continue; g_hash_table_insert (covered_uids, change->uid, NULL); if (!e_book_cache_get_contact_extra (book_cache, change->uid, &extra, cancellable, NULL)) extra = NULL; if (!e_book_cache_get_contact_custom_flags (book_cache, change->uid, &opflags, cancellable, NULL)) opflags = 0; if (change->state == E_OFFLINE_STATE_LOCALLY_CREATED || change->state == E_OFFLINE_STATE_LOCALLY_MODIFIED) { EContact *contact = NULL; success = e_book_cache_get_contact (book_cache, change->uid, FALSE, &contact, cancellable, error); if (success) { success = ebmb_save_contact_wrapper_sync (meta_backend, book_cache, change->state == E_OFFLINE_STATE_LOCALLY_MODIFIED, conflict_resolution, contact, extra, opflags, change->uid, NULL, NULL, NULL, cancellable, error); } g_clear_object (&contact); } else if (change->state == E_OFFLINE_STATE_LOCALLY_DELETED) { GError *local_error = NULL; success = e_book_meta_backend_remove_contact_sync (meta_backend, conflict_resolution, change->uid, extra, change->object, opflags, cancellable, &local_error); if (!success) { if (g_error_matches (local_error, E_BOOK_CLIENT_ERROR, E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND)) { g_clear_error (&local_error); success = TRUE; } else if (local_error) { g_propagate_error (error, local_error); } } } else { g_warn_if_reached (); } g_free (extra); } g_slist_free_full (offline_changes, e_cache_offline_change_free); g_hash_table_destroy (covered_uids); if (success) success = e_cache_clear_offline_changes (cache, cancellable, error); return success; } static void ebmb_foreach_cursor (EBookMetaBackend *meta_backend, EContact *contact, void (* func) (EDataBookCursor *cursor, EContact *contact)) { GSList *link; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_return_if_fail (func != NULL); g_mutex_lock (&meta_backend->priv->property_lock); for (link = meta_backend->priv->cursors; link; link = g_slist_next (link)) { EDataBookCursor *cursor = link->data; func (cursor, contact); } g_mutex_unlock (&meta_backend->priv->property_lock); } 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) { EBookBackend *book_backend; EContact *contact = NULL; GSList *local_photos, *link; GError *local_error = NULL; g_return_val_if_fail (uid != NULL, FALSE); if (!e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable, &local_error)) { if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) { g_clear_error (&local_error); return TRUE; } g_propagate_error (error, local_error); return FALSE; } book_backend = E_BOOK_BACKEND (meta_backend); if (!e_book_cache_remove_contact (book_cache, uid, opflags, offline_flag, cancellable, error)) { g_object_unref (contact); return FALSE; } local_photos = ebmb_gather_photos_local_filenames (meta_backend, contact); for (link = local_photos; link; link = g_slist_next (link)) { const gchar *filename = link->data; if (filename && g_unlink (filename) == -1) { /* Ignore these errors */ } } g_slist_free_full (local_photos, g_free); e_book_backend_notify_remove (book_backend, uid); ebmb_foreach_cursor (meta_backend, contact, e_data_book_cursor_contact_removed); g_object_unref (contact); return TRUE; } static void ebmb_ensure_refresh_timeout_set_locked (EBookMetaBackend *meta_backend); static gboolean ebmb_refresh_internal_sync (EBookMetaBackend *meta_backend, gboolean with_connection_error, GCancellable *cancellable, GError **error) { EBookCache *book_cache; gboolean success = FALSE, repeat = TRUE, is_repeat = FALSE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto done; /* Silently ignore the refresh request when in the power-saver mode */ if (ebmb_is_power_saver_enabled ()) { success = TRUE; goto done; } e_book_backend_foreach_view_notify_progress (E_BOOK_BACKEND (meta_backend), TRUE, 0, _("Refreshing…")); if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, with_connection_error ? error : NULL) || !e_backend_get_online (E_BACKEND (meta_backend))) { /* Failed connecting moves backend to offline */ g_mutex_lock (&meta_backend->priv->property_lock); meta_backend->priv->refresh_after_authenticate = TRUE; g_mutex_unlock (&meta_backend->priv->property_lock); goto done; } g_mutex_lock (&meta_backend->priv->property_lock); meta_backend->priv->refresh_after_authenticate = FALSE; g_mutex_unlock (&meta_backend->priv->property_lock); book_cache = e_book_meta_backend_ref_cache (meta_backend); if (!book_cache) { g_warn_if_reached (); goto done; } if (with_connection_error) { /* Skip upload when not initiated by the user (as part of the Refresh operation) */ success = TRUE; } else { GError *local_error = NULL; success = ebmb_upload_local_changes_sync (meta_backend, book_cache, E_CONFLICT_RESOLUTION_KEEP_SERVER, cancellable, &local_error); if (local_error) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) e_backend_set_online (E_BACKEND (meta_backend), FALSE); g_propagate_error (error, local_error); success = FALSE; } } e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_WRITE); while (repeat && success && !g_cancellable_set_error_if_cancelled (cancellable, error)) { GSList *created_objects = NULL, *modified_objects = NULL, *removed_objects = NULL; gchar *last_sync_tag, *new_sync_tag = NULL; GError *local_error = NULL; repeat = FALSE; last_sync_tag = e_cache_dup_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, NULL); if (last_sync_tag && !*last_sync_tag) { g_free (last_sync_tag); last_sync_tag = NULL; } success = e_book_meta_backend_get_changes_sync (meta_backend, last_sync_tag, is_repeat, &new_sync_tag, &repeat, &created_objects, &modified_objects, &removed_objects, cancellable, &local_error); if (local_error) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) e_backend_set_online (E_BACKEND (meta_backend), FALSE); g_propagate_error (error, local_error); local_error = NULL; success = FALSE; } if (success) { success = e_book_meta_backend_process_changes_sync (meta_backend, created_objects, modified_objects, removed_objects, cancellable, error); } if (success && new_sync_tag) e_cache_set_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, new_sync_tag, NULL); g_slist_free_full (created_objects, e_book_meta_backend_info_free); g_slist_free_full (modified_objects, e_book_meta_backend_info_free); g_slist_free_full (removed_objects, e_book_meta_backend_info_free); g_free (last_sync_tag); g_free (new_sync_tag); is_repeat = TRUE; } /* Always commit, to store at least what could be processed */ e_cache_unlock (E_CACHE (book_cache), E_CACHE_UNLOCK_COMMIT); g_object_unref (book_cache); done: g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->refresh_cancellable == cancellable) g_clear_object (&meta_backend->priv->refresh_cancellable); ebmb_ensure_refresh_timeout_set_locked (meta_backend); meta_backend->priv->last_refresh_time = g_get_real_time (); g_mutex_unlock (&meta_backend->priv->property_lock); e_book_backend_foreach_view_notify_progress (E_BOOK_BACKEND (meta_backend), TRUE, 0, NULL); g_signal_emit (meta_backend, signals[REFRESH_COMPLETED], 0, NULL); return success; } static void ebmb_refresh_thread_func (EBookBackend *book_backend, gpointer user_data, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); meta_backend = E_BOOK_META_BACKEND (book_backend); ebmb_refresh_internal_sync (meta_backend, FALSE, cancellable, error); } static void ebmb_source_refresh_timeout_cb (ESource *source, gpointer user_data) { GWeakRef *weak_ref = user_data; EBookMetaBackend *meta_backend; g_return_if_fail (weak_ref != NULL); meta_backend = g_weak_ref_get (weak_ref); if (meta_backend) { e_book_meta_backend_schedule_refresh (meta_backend); g_object_unref (meta_backend); } } /* Should hold the property_lock when calling this */ static void ebmb_ensure_refresh_timeout_set_locked (EBookMetaBackend *meta_backend) { if (!meta_backend->priv->refresh_timeout_id) { ESource *source = e_backend_get_source (E_BACKEND (meta_backend)); meta_backend->priv->refresh_timeout_id = e_source_refresh_add_timeout (source, NULL, ebmb_source_refresh_timeout_cb, e_weak_ref_new (meta_backend), (GDestroyNotify) e_weak_ref_free); } } static void ebmb_source_changed_thread_func (EBookBackend *book_backend, gpointer user_data, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return; meta_backend = E_BOOK_META_BACKEND (book_backend); g_mutex_lock (&meta_backend->priv->property_lock); ebmb_ensure_refresh_timeout_set_locked (meta_backend); g_mutex_unlock (&meta_backend->priv->property_lock); g_signal_emit (meta_backend, signals[SOURCE_CHANGED], 0, NULL); if (e_book_meta_backend_requires_reconnect (meta_backend) && (e_backend_get_online (E_BACKEND (meta_backend)) || e_backend_is_destination_reachable (E_BACKEND (meta_backend), cancellable, NULL))) { gboolean can_refresh; g_mutex_lock (&meta_backend->priv->connect_lock); can_refresh = e_book_meta_backend_disconnect_sync (meta_backend, cancellable, error); g_mutex_unlock (&meta_backend->priv->connect_lock); if (can_refresh) e_book_meta_backend_schedule_refresh (meta_backend); } g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->source_changed_cancellable == cancellable) g_clear_object (&meta_backend->priv->source_changed_cancellable); g_mutex_unlock (&meta_backend->priv->property_lock); } static void ebmb_go_offline_thread_func (EBookBackend *book_backend, gpointer user_data, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return; meta_backend = E_BOOK_META_BACKEND (book_backend); g_mutex_lock (&meta_backend->priv->connect_lock); e_book_meta_backend_disconnect_sync (meta_backend, cancellable, error); g_mutex_unlock (&meta_backend->priv->connect_lock); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->go_offline_cancellable == cancellable) g_clear_object (&meta_backend->priv->go_offline_cancellable); g_mutex_unlock (&meta_backend->priv->property_lock); } 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) { EContact *existing_contact = NULL; gboolean success = TRUE; g_return_val_if_fail (E_IS_CONTACT (contact), FALSE); success = e_book_meta_backend_store_inline_photos_sync (meta_backend, contact, cancellable, error); if (success && e_book_cache_get_contact (book_cache, e_contact_get_const (contact, E_CONTACT_UID), FALSE, &existing_contact, cancellable, NULL)) { GSList *old_photos, *new_photos, *link; old_photos = ebmb_gather_photos_local_filenames (meta_backend, existing_contact); if (old_photos) { GHashTable *photos_hash; photos_hash = g_hash_table_new (g_str_hash, g_str_equal); new_photos = ebmb_gather_photos_local_filenames (meta_backend, contact); for (link = new_photos; link; link = g_slist_next (link)) { const gchar *filename = link->data; if (filename) g_hash_table_insert (photos_hash, (gpointer) filename, NULL); } for (link = old_photos; link; link = g_slist_next (link)) { const gchar *filename = link->data; if (filename && !g_hash_table_contains (photos_hash, filename)) { if (g_unlink (filename) == -1) { /* Ignore these errors */ } } } g_slist_free_full (old_photos, g_free); g_slist_free_full (new_photos, g_free); g_hash_table_destroy (photos_hash); } } success = success && e_book_cache_put_contact (book_cache, contact, extra, opflags, offline_flag, cancellable, error); if (success) e_book_backend_notify_update (E_BOOK_BACKEND (meta_backend), contact); g_clear_object (&existing_contact); return success; } 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) { ECacheOfflineFlag offline_flag = E_CACHE_IS_ONLINE; EContact *contact = NULL; gchar *extra = NULL; gboolean success = TRUE; GError *local_error = NULL; if (preloaded_object && *preloaded_object) { contact = e_contact_new_from_vcard_with_uid (preloaded_object, uid); if (!contact) { g_propagate_error (error, e_client_error_create_fmt (E_CLIENT_ERROR_INVALID_ARG, _("Preloaded object for UID “%s” is invalid"), uid)); return FALSE; } } else if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error) || !e_book_meta_backend_load_contact_sync (meta_backend, uid, preloaded_extra, &contact, &extra, cancellable, error)) { g_free (extra); return FALSE; } else if (!contact) { g_propagate_error (error, e_client_error_create_fmt (E_CLIENT_ERROR_INVALID_ARG, _("Received object for UID “%s” is invalid"), uid)); g_free (extra); return FALSE; } success = ebmb_put_contact (meta_backend, book_cache, offline_flag, contact, extra ? extra : preloaded_extra, 0, cancellable, &local_error); if (success && out_new_uid) *out_new_uid = e_contact_get (contact, E_CONTACT_UID); g_object_unref (contact); g_free (extra); if (local_error) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) e_backend_set_online (E_BACKEND (meta_backend), FALSE); g_propagate_error (error, local_error); success = FALSE; } return success; } static gboolean ebmb_save_contact_wrapper_sync (EBookMetaBackend *meta_backend, EBookCache *book_cache, gboolean overwrite_existing, EConflictResolution conflict_resolution, /* const */ 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) { EContact *contact; gchar *new_uid = NULL, *new_extra = NULL; gboolean success = TRUE; GError *local_error = NULL; if (out_requires_put) *out_requires_put = TRUE; if (out_new_uid) *out_new_uid = NULL; contact = e_contact_duplicate (in_contact); success = e_book_meta_backend_inline_local_photos_sync (meta_backend, contact, cancellable, error); success = success && e_book_meta_backend_save_contact_sync (meta_backend, overwrite_existing, conflict_resolution, contact, extra, opflags, &new_uid, &new_extra, cancellable, &local_error); if (success && new_uid && *new_uid) { gchar *loaded_uid = NULL; success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, new_uid, NULL, new_extra ? new_extra : extra, &loaded_uid, cancellable, error); if (success && g_strcmp0 (loaded_uid, orig_uid) != 0) success = ebmb_maybe_remove_from_cache (meta_backend, book_cache, E_CACHE_IS_ONLINE, orig_uid, opflags, cancellable, error); if (success && out_new_uid) *out_new_uid = loaded_uid; else g_free (loaded_uid); if (out_requires_put) *out_requires_put = FALSE; } g_free (new_uid); if (success && out_new_extra) *out_new_extra = new_extra; else g_free (new_extra); g_object_unref (contact); if (local_error) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) e_backend_set_online (E_BACKEND (meta_backend), FALSE); g_propagate_error (error, local_error); success = FALSE; } return success; } static gchar * ebmb_get_backend_property (EBookBackend *book_backend, const gchar *prop_name) { g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL); g_return_val_if_fail (prop_name != NULL, NULL); if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REVISION)) { EBookCache *book_cache; gchar *revision = NULL; book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend)); if (book_cache) { revision = e_cache_dup_revision (E_CACHE (book_cache)); g_object_unref (book_cache); } else { g_warn_if_reached (); } return revision; } else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) { return g_strdup (e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend))); } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) { return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS)); } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) { GString *fields; gint ii; fields = g_string_sized_new (1024); /* Claim to support everything by default */ for (ii = 1; ii < E_CONTACT_FIELD_LAST; ii++) { if (fields->len > 0) g_string_append_c (fields, ','); g_string_append (fields, e_contact_field_name (ii)); } return g_string_free (fields, FALSE); } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_CATEGORIES)) { EBookCache *book_cache; gchar *categories = NULL; book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend)); if (book_cache) { categories = e_book_cache_dup_categories (book_cache); g_object_unref (book_cache); } else { g_warn_if_reached (); } return categories; } /* Chain up to parent's method. */ return E_BOOK_BACKEND_CLASS (e_book_meta_backend_parent_class)->impl_get_backend_property (book_backend, prop_name); } static gboolean ebmb_open_sync (EBookBackendSync *book_backend, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; ESource *source; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); if (e_book_backend_is_opened (E_BOOK_BACKEND (book_backend))) return TRUE; meta_backend = E_BOOK_META_BACKEND (book_backend); if (meta_backend->priv->create_cache_error) { g_propagate_error (error, meta_backend->priv->create_cache_error); meta_backend->priv->create_cache_error = NULL; return FALSE; } source = e_backend_get_source (E_BACKEND (book_backend)); if (!meta_backend->priv->source_changed_id) { meta_backend->priv->source_changed_id = g_signal_connect_swapped (source, "changed", G_CALLBACK (ebmb_schedule_source_changed), meta_backend); } if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) { ESourceWebdav *webdav_extension; webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); e_source_webdav_unset_temporary_ssl_trust (webdav_extension); } if (e_book_meta_backend_get_ever_connected (meta_backend)) { e_book_backend_set_writable (E_BOOK_BACKEND (meta_backend), e_book_meta_backend_get_connected_writable (meta_backend)); } else { if (!e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error)) { g_mutex_lock (&meta_backend->priv->property_lock); meta_backend->priv->refresh_after_authenticate = TRUE; g_mutex_unlock (&meta_backend->priv->property_lock); return FALSE; } } e_book_meta_backend_schedule_refresh (E_BOOK_META_BACKEND (book_backend)); return TRUE; } static gboolean ebmb_refresh_sync (EBookBackendSync *book_backend, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBackend *backend; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); meta_backend = E_BOOK_META_BACKEND (book_backend); backend = E_BACKEND (meta_backend); if (!e_backend_get_online (backend) && e_backend_is_destination_reachable (backend, cancellable, NULL)) e_backend_set_online (backend, TRUE); if (!e_backend_get_online (backend)) return TRUE; if (ebmb_is_power_saver_enabled ()) { g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OTHER_ERROR, _("Refresh skipped due to enabled Power Saver mode. Disable Power Saver mode and repeat the action.")); return FALSE; } success = e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, error); if (success) e_book_meta_backend_schedule_refresh (meta_backend); return success; } 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) { const gchar *uid; gchar *new_uid = NULL, *new_extra = NULL; gboolean success, requires_put = TRUE; g_return_val_if_fail (E_IS_CONTACT (contact), FALSE); uid = e_contact_get_const (contact, E_CONTACT_UID); if (!uid) { gchar *new_uid; new_uid = e_util_generate_uid (); if (!new_uid) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL)); return FALSE; } e_contact_set (contact, E_CONTACT_UID, new_uid); uid = e_contact_get_const (contact, E_CONTACT_UID); g_free (new_uid); } if (e_cache_contains (E_CACHE (book_cache), uid, E_CACHE_EXCLUDE_DELETED)) { g_propagate_error (error, e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_ID_ALREADY_EXISTS, NULL)); return FALSE; } if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) { if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) { *offline_flag = E_CACHE_IS_ONLINE; } else { *offline_flag = E_CACHE_IS_OFFLINE; } } if (*offline_flag == E_CACHE_IS_ONLINE) { if (!ebmb_save_contact_wrapper_sync (meta_backend, book_cache, FALSE, conflict_resolution, contact, NULL, opflags, uid, &requires_put, &new_uid, &new_extra, cancellable, error)) { return FALSE; } } if (requires_put) { success = e_book_cache_put_contact (book_cache, contact, new_extra, opflags, *offline_flag, cancellable, error); if (success) e_book_backend_notify_update (E_BOOK_BACKEND (meta_backend), contact); } else { success = TRUE; } if (success) { if (out_new_uid) *out_new_uid = g_strdup (new_uid ? new_uid : uid); if (out_new_contact) { if (new_uid) { if (!e_book_cache_get_contact (book_cache, new_uid, FALSE, out_new_contact, cancellable, NULL)) *out_new_contact = g_object_ref (contact); } else { *out_new_contact = g_object_ref (contact); } } } g_free (new_uid); g_free (new_extra); return success; } static gboolean ebmb_create_contacts_sync (EBookBackendSync *book_backend, const gchar * const *vcards, guint32 opflags, GSList **out_contacts, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN; EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags); gint ii; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (vcards != NULL, FALSE); g_return_val_if_fail (out_contacts != NULL, FALSE); *out_contacts = NULL; if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL)); return FALSE; } meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); for (ii = 0; vcards[ii] && success; ii++) { EContact *contact, *new_contact = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) { success = FALSE; break; } contact = e_contact_new_from_vcard (vcards[ii]); if (!contact) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL)); success = FALSE; break; } success = ebmb_create_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution, opflags, contact, NULL, &new_contact, cancellable, error); if (success) { ebmb_foreach_cursor (meta_backend, new_contact, e_data_book_cursor_contact_added); *out_contacts = g_slist_prepend (*out_contacts, new_contact); } g_object_unref (contact); } g_object_unref (book_cache); if (success) { *out_contacts = g_slist_reverse (*out_contacts); } else { g_slist_free_full (*out_contacts, g_object_unref); *out_contacts = NULL; } return success; } 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) { const gchar *uid; EContact *existing_contact = NULL; gchar *extra = NULL, *new_uid = NULL, *new_extra = NULL; gboolean success = TRUE, requires_put = TRUE; GError *local_error = NULL; g_return_val_if_fail (contact != NULL, FALSE); uid = e_contact_get_const (contact, E_CONTACT_UID); if (!uid) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL)); return FALSE; } if (!e_book_cache_get_contact (book_cache, uid, FALSE, &existing_contact, cancellable, &local_error)) { if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) { g_clear_error (&local_error); local_error = e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL); } g_propagate_error (error, local_error); return FALSE; } if (!e_book_cache_get_contact_extra (book_cache, uid, &extra, cancellable, NULL)) extra = NULL; if (success && *offline_flag == E_CACHE_OFFLINE_UNKNOWN) { if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) { *offline_flag = E_CACHE_IS_ONLINE; } else { *offline_flag = E_CACHE_IS_OFFLINE; } } if (success && *offline_flag == E_CACHE_IS_ONLINE) { success = ebmb_save_contact_wrapper_sync (meta_backend, book_cache, TRUE, conflict_resolution, contact, extra, opflags, uid, &requires_put, &new_uid, &new_extra, cancellable, error); } if (success && requires_put) success = ebmb_put_contact (meta_backend, book_cache, *offline_flag, contact, new_extra ? new_extra : extra, opflags, cancellable, error); if (success && out_new_contact) { if (new_uid) { if (!e_book_cache_get_contact (book_cache, new_uid, FALSE, out_new_contact, cancellable, NULL)) *out_new_contact = NULL; } else { *out_new_contact = g_object_ref (contact); } } g_clear_object (&existing_contact); g_free (new_extra); g_free (new_uid); g_free (extra); return success; } static gboolean ebmb_modify_contacts_sync (EBookBackendSync *book_backend, const gchar * const *vcards, guint32 opflags, GSList **out_contacts, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN; EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags); gint ii; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (vcards != NULL, FALSE); g_return_val_if_fail (out_contacts != NULL, FALSE); *out_contacts = NULL; if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL)); return FALSE; } meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); for (ii = 0; vcards[ii] && success; ii++) { EContact *contact, *new_contact = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) { success = FALSE; break; } contact = e_contact_new_from_vcard (vcards[ii]); if (!contact) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL)); success = FALSE; break; } success = ebmb_modify_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution, opflags, contact, &new_contact, cancellable, error); if (success && new_contact) { ebmb_foreach_cursor (meta_backend, contact, e_data_book_cursor_contact_removed); ebmb_foreach_cursor (meta_backend, new_contact, e_data_book_cursor_contact_added); *out_contacts = g_slist_prepend (*out_contacts, g_object_ref (new_contact)); } g_clear_object (&new_contact); g_object_unref (contact); } g_object_unref (book_cache); if (success) { *out_contacts = g_slist_reverse (*out_contacts); } else { g_slist_free_full (*out_contacts, g_object_unref); *out_contacts = NULL; } return success; } 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) { EContact *existing_contact = NULL; gchar *extra = NULL; gboolean success = TRUE; GError *local_error = NULL; g_return_val_if_fail (uid != NULL, FALSE); if (!e_book_cache_get_contact (book_cache, uid, FALSE, &existing_contact, cancellable, &local_error)) { if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) { g_clear_error (&local_error); local_error = e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL); } g_propagate_error (error, local_error); return FALSE; } if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) { if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL)) { *offline_flag = E_CACHE_IS_ONLINE; } else { *offline_flag = E_CACHE_IS_OFFLINE; } } if (!e_book_cache_get_contact_extra (book_cache, uid, &extra, cancellable, NULL)) extra = NULL; if (*offline_flag == E_CACHE_IS_ONLINE && e_cache_get_offline_state (E_CACHE (book_cache), uid, cancellable, NULL) != E_OFFLINE_STATE_LOCALLY_CREATED) { gchar *vcard_string = NULL; g_warn_if_fail (e_book_cache_get_vcard (book_cache, uid, FALSE, &vcard_string, cancellable, NULL)); success = e_book_meta_backend_remove_contact_sync (meta_backend, conflict_resolution, uid, extra, vcard_string, opflags, cancellable, &local_error); g_free (vcard_string); if (local_error) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) e_backend_set_online (E_BACKEND (meta_backend), FALSE); g_propagate_error (error, local_error); success = FALSE; } } success = success && ebmb_maybe_remove_from_cache (meta_backend, book_cache, *offline_flag, uid, opflags, cancellable, error); g_clear_object (&existing_contact); g_free (extra); return success; } static gboolean ebmb_remove_contacts_sync (EBookBackendSync *book_backend, const gchar * const *uids, guint32 opflags, GSList **out_removed_uids, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN; EConflictResolution conflict_resolution = e_book_util_operation_flags_to_conflict_resolution (opflags); gint ii; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (uids != NULL, FALSE); g_return_val_if_fail (out_removed_uids != NULL, FALSE); *out_removed_uids = NULL; if (!e_book_backend_get_writable (E_BOOK_BACKEND (book_backend))) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_PERMISSION_DENIED, NULL)); return FALSE; } meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); for (ii = 0; uids[ii] && success; ii++) { const gchar *uid = uids[ii]; if (g_cancellable_set_error_if_cancelled (cancellable, error)) { success = FALSE; break; } if (!uid) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL)); success = FALSE; break; } success = ebmb_remove_contact_sync (meta_backend, book_cache, &offline_flag, conflict_resolution, opflags, uid, cancellable, error); if (success) *out_removed_uids = g_slist_prepend (*out_removed_uids, g_strdup (uid)); } g_object_unref (book_cache); *out_removed_uids = g_slist_reverse (*out_removed_uids); return success; } static EContact * ebmb_get_contact_sync (EBookBackendSync *book_backend, const gchar *uid, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; EContact *contact = NULL; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL); g_return_val_if_fail (uid && *uid, NULL); meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, NULL); if (!e_book_cache_get_contact (book_cache, uid, FALSE, &contact, cancellable, &local_error) && g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) { gchar *loaded_uid = NULL; gboolean found = FALSE; g_clear_error (&local_error); /* Ignore errors here, just try whether it's on the remote side, but not in the local cache */ if (e_book_meta_backend_ensure_connected_sync (meta_backend, cancellable, NULL) && ebmb_load_contact_wrapper_sync (meta_backend, book_cache, uid, NULL, NULL, &loaded_uid, cancellable, NULL)) { found = e_book_cache_get_contact (book_cache, loaded_uid, FALSE, &contact, cancellable, NULL); } if (!found) g_propagate_error (error, e_book_client_error_create (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, NULL)); g_free (loaded_uid); } else if (local_error) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_OTHER_ERROR, local_error->message)); g_clear_error (&local_error); } g_object_unref (book_cache); return contact; } static gboolean ebmb_get_contact_list_sync (EBookBackendSync *book_backend, const gchar *query, GSList **out_contacts, GCancellable *cancellable, GError **error) { g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (out_contacts != NULL, FALSE); *out_contacts = NULL; return e_book_meta_backend_search_sync (E_BOOK_META_BACKEND (book_backend), query, FALSE, out_contacts, cancellable, error); } static gboolean ebmb_get_contact_list_uids_sync (EBookBackendSync *book_backend, const gchar *query, GSList **out_uids, GCancellable *cancellable, GError **error) { g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (out_uids != NULL, FALSE); *out_uids = NULL; return e_book_meta_backend_search_uids_sync (E_BOOK_META_BACKEND (book_backend), query, out_uids, cancellable, error); } static gboolean ebmb_contains_email_sync (EBookBackendSync *book_backend, const gchar *email_address, GCancellable *cancellable, GError **error) { EBookCache *cache; gboolean found; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); g_return_val_if_fail (email_address != NULL, FALSE); cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend)); if (!cache) return FALSE; found = e_book_cache_contains_email (cache, email_address, cancellable, error); g_object_unref (cache); return found; } static void ebmb_start_view (EBookBackend *book_backend, EDataBookView *view) { GCancellable *cancellable; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); cancellable = ebmb_create_view_cancellable (E_BOOK_META_BACKEND (book_backend), view); e_book_backend_schedule_custom_operation (book_backend, cancellable, ebmb_start_view_thread_func, g_object_ref (view), g_object_unref); g_object_unref (cancellable); } static void ebmb_stop_view (EBookBackend *book_backend, EDataBookView *view) { GCancellable *cancellable; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); cancellable = ebmb_steal_view_cancellable (E_BOOK_META_BACKEND (book_backend), view); if (cancellable) { g_cancellable_cancel (cancellable); g_object_unref (cancellable); } } static EDataBookDirect * ebmb_get_direct_book (EBookBackend *book_backend) { EBookMetaBackendClass *klass; EBookCache *book_cache; EDataBookDirect *direct_book; const gchar *cache_filename; gchar *backend_path; gchar *dirname; const gchar *modules_env; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL); klass = E_BOOK_META_BACKEND_GET_CLASS (book_backend); g_return_val_if_fail (klass != NULL, NULL); if (!klass->backend_module_filename || !klass->backend_factory_type_name) return NULL; book_cache = e_book_meta_backend_ref_cache (E_BOOK_META_BACKEND (book_backend)); g_return_val_if_fail (book_cache != NULL, NULL); cache_filename = e_cache_get_filename (E_CACHE (book_cache)); dirname = g_path_get_dirname (cache_filename); modules_env = g_getenv (EDS_ADDRESS_BOOK_MODULES); /* Support in-tree testing / relocated modules */ if (modules_env) { backend_path = g_build_filename (modules_env, klass->backend_module_filename, NULL); } else if (klass->backend_module_directory) { backend_path = g_build_filename (klass->backend_module_directory, klass->backend_module_filename, NULL); } else { backend_path = g_build_filename (BACKENDDIR, klass->backend_module_filename, NULL); } direct_book = e_data_book_direct_new (backend_path, klass->backend_factory_type_name, dirname); g_object_unref (book_cache); g_free (backend_path); g_free (dirname); return direct_book; } static void ebmb_configure_direct (EBookBackend *book_backend, const gchar *base_directory) { EBookMetaBackend *meta_backend; EBookCache *book_cache; const gchar *cache_filename; gchar *dirname; g_return_if_fail (E_IS_BOOK_META_BACKEND (book_backend)); if (!base_directory) return; meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_if_fail (book_cache != NULL); cache_filename = e_cache_get_filename (E_CACHE (book_cache)); dirname = g_path_get_dirname (cache_filename); /* Did path for the cache change? Change the cache as well */ if (dirname && !g_str_equal (base_directory, dirname) && !g_str_has_prefix (dirname, base_directory)) { gchar *filename = g_path_get_basename (cache_filename); gchar *new_cache_filename; EBookCache *new_cache; ESource *source; new_cache_filename = g_build_filename (base_directory, filename, NULL); source = e_backend_get_source (E_BACKEND (book_backend)); g_clear_error (&meta_backend->priv->create_cache_error); new_cache = e_book_cache_new (new_cache_filename, source, NULL, &meta_backend->priv->create_cache_error); g_prefix_error (&meta_backend->priv->create_cache_error, _("Failed to create cache “%s”:"), new_cache_filename); if (new_cache) { e_book_meta_backend_set_cache (meta_backend, new_cache); g_clear_object (&new_cache); } g_free (new_cache_filename); g_free (filename); } g_free (dirname); g_object_unref (book_cache); } static gboolean ebmb_set_locale (EBookBackend *book_backend, const gchar *locale, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); success = e_book_cache_set_locale (book_cache, locale, cancellable, error); if (success) { GSList *link; g_mutex_lock (&meta_backend->priv->property_lock); for (link = meta_backend->priv->cursors; success && link; link = g_slist_next (link)) { EDataBookCursor *cursor = link->data; success = e_data_book_cursor_load_locale (cursor, NULL, cancellable, error); } g_mutex_unlock (&meta_backend->priv->property_lock); } g_object_unref (book_cache); return success; } static gchar * ebmb_dup_locale (EBookBackend *book_backend) { EBookMetaBackend *meta_backend; EBookCache *book_cache; gchar *locale; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL); meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, NULL); locale = e_book_cache_dup_locale (book_cache); g_object_unref (book_cache); return locale; } static EDataBookCursor * ebmb_create_cursor (EBookBackend *book_backend, EContactField *sort_fields, EBookCursorSortType *sort_types, guint n_fields, GError **error) { EBookMetaBackend *meta_backend; EBookCache *book_cache; EDataBookCursor *cursor; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), NULL); meta_backend = E_BOOK_META_BACKEND (book_backend); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, NULL); cursor = e_data_book_cursor_cache_new (book_backend, book_cache, sort_fields, sort_types, n_fields, error); if (cursor) { g_mutex_lock (&meta_backend->priv->property_lock); meta_backend->priv->cursors = g_slist_prepend (meta_backend->priv->cursors, cursor); g_mutex_unlock (&meta_backend->priv->property_lock); } g_object_unref (book_cache); return cursor; } static gboolean ebmb_delete_cursor (EBookBackend *book_backend, EDataBookCursor *cursor, GError **error) { EBookMetaBackend *meta_backend; GSList *link; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (book_backend), FALSE); meta_backend = E_BOOK_META_BACKEND (book_backend); g_mutex_lock (&meta_backend->priv->property_lock); link = g_slist_find (meta_backend->priv->cursors, cursor); if (link) { meta_backend->priv->cursors = g_slist_remove (meta_backend->priv->cursors, cursor); g_object_unref (cursor); } else { g_set_error_literal ( error, E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG, _("Requested to delete an unrelated cursor")); } g_mutex_unlock (&meta_backend->priv->property_lock); return link != NULL; } static ESourceAuthenticationResult ebmb_authenticate_sync (EBackend *backend, const ENamedParameters *credentials, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors, GCancellable *cancellable, GError **error) { EBookMetaBackend *meta_backend; ESourceAuthenticationResult auth_result = E_SOURCE_AUTHENTICATION_UNKNOWN; gboolean success, refresh_after_authenticate = FALSE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (backend), E_SOURCE_AUTHENTICATION_ERROR); meta_backend = E_BOOK_META_BACKEND (backend); if (!e_backend_get_online (backend) && !e_backend_is_destination_reachable (backend, cancellable, NULL)) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_REPOSITORY_OFFLINE, NULL)); g_mutex_lock (&meta_backend->priv->wait_credentials_lock); meta_backend->priv->wait_credentials_stamp++; g_cond_broadcast (&meta_backend->priv->wait_credentials_cond); g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); return E_SOURCE_AUTHENTICATION_ERROR; } g_mutex_lock (&meta_backend->priv->connect_lock); /* Always disconnect first, then provide new credentials. */ e_book_meta_backend_disconnect_sync (meta_backend, cancellable, NULL); e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_CONNECTING); success = e_book_meta_backend_connect_sync (meta_backend, credentials, &auth_result, out_certificate_pem, out_certificate_errors, cancellable, error); if (success) { ebmb_update_connection_values (meta_backend); auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED; e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_CONNECTED); } else { if (auth_result == E_SOURCE_AUTHENTICATION_UNKNOWN) auth_result = E_SOURCE_AUTHENTICATION_ERROR; e_source_set_connection_status (e_backend_get_source (backend), E_SOURCE_CONNECTION_STATUS_DISCONNECTED); } g_mutex_unlock (&meta_backend->priv->connect_lock); g_mutex_lock (&meta_backend->priv->property_lock); e_named_parameters_free (meta_backend->priv->last_credentials); if (success) { meta_backend->priv->last_credentials = e_named_parameters_new_clone (credentials); refresh_after_authenticate = meta_backend->priv->refresh_after_authenticate; meta_backend->priv->refresh_after_authenticate = FALSE; } else { meta_backend->priv->last_credentials = NULL; } g_mutex_unlock (&meta_backend->priv->property_lock); g_mutex_lock (&meta_backend->priv->wait_credentials_lock); meta_backend->priv->wait_credentials_stamp++; g_cond_broadcast (&meta_backend->priv->wait_credentials_cond); g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); if (refresh_after_authenticate) e_book_meta_backend_schedule_refresh (meta_backend); return auth_result; } static void ebmb_schedule_source_changed (EBookMetaBackend *meta_backend) { GCancellable *cancellable; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->source_changed_cancellable) { /* Already updating */ g_mutex_unlock (&meta_backend->priv->property_lock); return; } cancellable = g_cancellable_new (); meta_backend->priv->source_changed_cancellable = g_object_ref (cancellable); g_mutex_unlock (&meta_backend->priv->property_lock); e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable, ebmb_source_changed_thread_func, NULL, NULL); g_object_unref (cancellable); } static void ebmb_schedule_go_offline (EBookMetaBackend *meta_backend) { GCancellable *cancellable; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->property_lock); /* Cancel anything ongoing now, but disconnect in a dedicated thread */ if (meta_backend->priv->refresh_cancellable) { g_cancellable_cancel (meta_backend->priv->refresh_cancellable); g_clear_object (&meta_backend->priv->refresh_cancellable); } if (meta_backend->priv->source_changed_cancellable) { g_cancellable_cancel (meta_backend->priv->source_changed_cancellable); g_clear_object (&meta_backend->priv->source_changed_cancellable); } if (meta_backend->priv->go_offline_cancellable) { /* Already going offline */ g_mutex_unlock (&meta_backend->priv->property_lock); return; } cancellable = g_cancellable_new (); meta_backend->priv->go_offline_cancellable = g_object_ref (cancellable); g_mutex_unlock (&meta_backend->priv->property_lock); e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable, ebmb_go_offline_thread_func, NULL, NULL); g_object_unref (cancellable); } static void ebmb_notify_online_cb (GObject *object, GParamSpec *param, gpointer user_data) { EBookMetaBackend *meta_backend = user_data; gboolean new_value; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); new_value = e_backend_get_online (E_BACKEND (meta_backend)); if (!new_value == !meta_backend->priv->current_online_state) return; meta_backend->priv->current_online_state = new_value; if (new_value) { gint64 now = g_get_real_time (); /* Do not auto-run refresh (due to getting online) more than once per hour */ if (now - meta_backend->priv->last_refresh_time >= G_USEC_PER_SEC * ((gint64) 60) * 60) { meta_backend->priv->last_refresh_time = now; e_book_meta_backend_schedule_refresh (meta_backend); } } else { ebmb_schedule_go_offline (meta_backend); } } static void ebmb_cancel_view_cb (gpointer key, gpointer value, gpointer user_data) { GCancellable *cancellable = value; g_return_if_fail (G_IS_CANCELLABLE (cancellable)); g_cancellable_cancel (cancellable); } static void ebmb_wait_for_credentials_cancelled_cb (GCancellable *cancellable, gpointer user_data) { EBookMetaBackend *meta_backend = user_data; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->wait_credentials_lock); g_cond_broadcast (&meta_backend->priv->wait_credentials_cond); g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); } static gboolean ebmb_maybe_wait_for_credentials (EBookMetaBackend *meta_backend, guint wait_credentials_stamp, const GError *op_error, GCancellable *cancellable) { EBackend *backend; ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_UNKNOWN; gchar *certificate_pem = NULL; GTlsCertificateFlags certificate_errors = 0; gboolean got_credentials = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); if (!op_error || g_cancellable_is_cancelled (cancellable)) return FALSE; if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_TLS_NOT_AVAILABLE) && e_book_meta_backend_get_ssl_error_details (meta_backend, &certificate_pem, &certificate_errors)) { reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED; } else if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_REQUIRED)) { reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED; } else if (g_error_matches (op_error, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) { reason = E_SOURCE_CREDENTIALS_REASON_REJECTED; } if (reason == E_SOURCE_CREDENTIALS_REASON_UNKNOWN) return FALSE; backend = E_BACKEND (meta_backend); g_mutex_lock (&meta_backend->priv->wait_credentials_lock); if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp || e_backend_credentials_required_sync (backend, reason, certificate_pem, certificate_errors, op_error, cancellable, &local_error)) { gint64 wait_end_time; gulong handler_id; wait_end_time = g_get_monotonic_time () + MAX_WAIT_FOR_CREDENTIALS_SECS * G_TIME_SPAN_SECOND; handler_id = cancellable ? g_signal_connect (cancellable, "cancelled", G_CALLBACK (ebmb_wait_for_credentials_cancelled_cb), meta_backend) : 0; while (wait_credentials_stamp == meta_backend->priv->wait_credentials_stamp && !g_cancellable_is_cancelled (cancellable)) { if (!g_cond_wait_until (&meta_backend->priv->wait_credentials_cond, &meta_backend->priv->wait_credentials_lock, wait_end_time)) break; } if (handler_id) g_signal_handler_disconnect (cancellable, handler_id); if (wait_credentials_stamp != meta_backend->priv->wait_credentials_stamp) got_credentials = e_source_get_connection_status (e_backend_get_source (backend)) == E_SOURCE_CONNECTION_STATUS_CONNECTED; } else { g_warning ("%s: Failed to call credentials required: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error"); } g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); g_free (certificate_pem); return got_credentials; } static void e_book_meta_backend_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CACHE: e_book_meta_backend_set_cache ( E_BOOK_META_BACKEND (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_book_meta_backend_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CACHE: g_value_take_object ( value, e_book_meta_backend_ref_cache ( E_BOOK_META_BACKEND (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_book_meta_backend_constructed (GObject *object) { EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_book_meta_backend_parent_class)->constructed (object); meta_backend->priv->current_online_state = e_backend_get_online (E_BACKEND (meta_backend)); meta_backend->priv->notify_online_id = g_signal_connect (meta_backend, "notify::online", G_CALLBACK (ebmb_notify_online_cb), meta_backend); if (!meta_backend->priv->cache) { EBookCache *cache; ESource *source; gchar *filename; source = e_backend_get_source (E_BACKEND (meta_backend)); filename = g_build_filename (e_book_backend_get_cache_dir (E_BOOK_BACKEND (meta_backend)), "cache.db", NULL); cache = e_book_cache_new (filename, source, NULL, &meta_backend->priv->create_cache_error); g_prefix_error (&meta_backend->priv->create_cache_error, _("Failed to create cache “%s”:"), filename); g_free (filename); if (cache) { e_book_meta_backend_set_cache (meta_backend, cache); g_clear_object (&cache); } } } static void e_book_meta_backend_dispose (GObject *object) { EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object); ESource *source = e_backend_get_source (E_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->cursors) { g_slist_free_full (meta_backend->priv->cursors, g_object_unref); meta_backend->priv->cursors = NULL; } if (meta_backend->priv->refresh_timeout_id) { if (source) e_source_refresh_remove_timeout (source, meta_backend->priv->refresh_timeout_id); meta_backend->priv->refresh_timeout_id = 0; } if (meta_backend->priv->source_changed_id) { if (source) g_signal_handler_disconnect (source, meta_backend->priv->source_changed_id); meta_backend->priv->source_changed_id = 0; } if (meta_backend->priv->notify_online_id) { g_signal_handler_disconnect (meta_backend, meta_backend->priv->notify_online_id); meta_backend->priv->notify_online_id = 0; } if (meta_backend->priv->revision_changed_id) { if (meta_backend->priv->cache) g_signal_handler_disconnect (meta_backend->priv->cache, meta_backend->priv->revision_changed_id); meta_backend->priv->revision_changed_id = 0; } if (meta_backend->priv->categories_changed_id) { if (meta_backend->priv->cache) g_signal_handler_disconnect (meta_backend->priv->cache, meta_backend->priv->categories_changed_id); meta_backend->priv->categories_changed_id = 0; } g_hash_table_foreach (meta_backend->priv->view_cancellables, ebmb_cancel_view_cb, NULL); if (meta_backend->priv->refresh_cancellable) { g_cancellable_cancel (meta_backend->priv->refresh_cancellable); g_clear_object (&meta_backend->priv->refresh_cancellable); } if (meta_backend->priv->source_changed_cancellable) { g_cancellable_cancel (meta_backend->priv->source_changed_cancellable); g_clear_object (&meta_backend->priv->source_changed_cancellable); } if (meta_backend->priv->go_offline_cancellable) { g_cancellable_cancel (meta_backend->priv->go_offline_cancellable); g_clear_object (&meta_backend->priv->go_offline_cancellable); } e_named_parameters_free (meta_backend->priv->last_credentials); meta_backend->priv->last_credentials = NULL; g_mutex_unlock (&meta_backend->priv->property_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_book_meta_backend_parent_class)->dispose (object); } static void e_book_meta_backend_finalize (GObject *object) { EBookMetaBackend *meta_backend = E_BOOK_META_BACKEND (object); g_clear_object (&meta_backend->priv->cache); g_clear_object (&meta_backend->priv->refresh_cancellable); g_clear_object (&meta_backend->priv->source_changed_cancellable); g_clear_object (&meta_backend->priv->go_offline_cancellable); g_clear_error (&meta_backend->priv->create_cache_error); g_clear_pointer (&meta_backend->priv->authentication_host, g_free); g_clear_pointer (&meta_backend->priv->authentication_user, g_free); g_clear_pointer (&meta_backend->priv->authentication_method, g_free); g_clear_pointer (&meta_backend->priv->authentication_proxy_uid, g_free); g_clear_pointer (&meta_backend->priv->authentication_credential_name, g_free); g_clear_pointer (&meta_backend->priv->webdav_uri, g_uri_unref); g_mutex_clear (&meta_backend->priv->connect_lock); g_mutex_clear (&meta_backend->priv->property_lock); g_mutex_clear (&meta_backend->priv->wait_credentials_lock); g_cond_clear (&meta_backend->priv->wait_credentials_cond); g_hash_table_destroy (meta_backend->priv->view_cancellables); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_book_meta_backend_parent_class)->finalize (object); } static void e_book_meta_backend_class_init (EBookMetaBackendClass *klass) { GObjectClass *object_class; EBackendClass *backend_class; EBookBackendClass *book_backend_class; EBookBackendSyncClass *book_backend_sync_class; klass->backend_factory_type_name = NULL; klass->backend_module_filename = NULL; klass->get_changes_sync = ebmb_get_changes_sync; klass->search_sync = ebmb_search_sync; klass->search_uids_sync = ebmb_search_uids_sync; klass->requires_reconnect = ebmb_requires_reconnect; klass->get_ssl_error_details = ebmb_get_ssl_error_details; book_backend_sync_class = E_BOOK_BACKEND_SYNC_CLASS (klass); book_backend_sync_class->open_sync = ebmb_open_sync; book_backend_sync_class->refresh_sync = ebmb_refresh_sync; book_backend_sync_class->create_contacts_sync = ebmb_create_contacts_sync; book_backend_sync_class->modify_contacts_sync = ebmb_modify_contacts_sync; book_backend_sync_class->remove_contacts_sync = ebmb_remove_contacts_sync; book_backend_sync_class->get_contact_sync = ebmb_get_contact_sync; book_backend_sync_class->get_contact_list_sync = ebmb_get_contact_list_sync; book_backend_sync_class->get_contact_list_uids_sync = ebmb_get_contact_list_uids_sync; book_backend_sync_class->contains_email_sync = ebmb_contains_email_sync; book_backend_class = E_BOOK_BACKEND_CLASS (klass); book_backend_class->impl_get_backend_property = ebmb_get_backend_property; book_backend_class->impl_start_view = ebmb_start_view; book_backend_class->impl_stop_view = ebmb_stop_view; book_backend_class->impl_get_direct_book = ebmb_get_direct_book; book_backend_class->impl_configure_direct = ebmb_configure_direct; book_backend_class->impl_set_locale = ebmb_set_locale; book_backend_class->impl_dup_locale = ebmb_dup_locale; book_backend_class->impl_create_cursor = ebmb_create_cursor; book_backend_class->impl_delete_cursor = ebmb_delete_cursor; backend_class = E_BACKEND_CLASS (klass); backend_class->authenticate_sync = ebmb_authenticate_sync; object_class = G_OBJECT_CLASS (klass); object_class->set_property = e_book_meta_backend_set_property; object_class->get_property = e_book_meta_backend_get_property; object_class->constructed = e_book_meta_backend_constructed; object_class->dispose = e_book_meta_backend_dispose; object_class->finalize = e_book_meta_backend_finalize; /** * EBookMetaBackend:cache: * * The #EBookCache being used for this meta backend. **/ g_object_class_install_property ( object_class, PROP_CACHE, g_param_spec_object ( "cache", "Cache", "Book Cache", E_TYPE_BOOK_CACHE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /* This signal is meant for testing purposes mainly */ signals[REFRESH_COMPLETED] = g_signal_new ( "refresh-completed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE); /** * EBookMetaBackend::source-changed * * This signal is emitted whenever the underlying backend #ESource * changes. Unlike the #ESource's 'changed' signal this one is * tight to the #EBookMetaBackend itself and is emitted from * a dedicated thread, thus it doesn't block the main thread. * * Since: 3.26 **/ signals[SOURCE_CHANGED] = g_signal_new ( "source-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EBookMetaBackendClass, source_changed), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE); } static void e_book_meta_backend_init (EBookMetaBackend *meta_backend) { meta_backend->priv = e_book_meta_backend_get_instance_private (meta_backend); g_mutex_init (&meta_backend->priv->connect_lock); g_mutex_init (&meta_backend->priv->property_lock); g_mutex_init (&meta_backend->priv->wait_credentials_lock); g_cond_init (&meta_backend->priv->wait_credentials_cond); meta_backend->priv->view_cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); meta_backend->priv->current_online_state = FALSE; meta_backend->priv->refresh_after_authenticate = FALSE; meta_backend->priv->ever_connected = -1; meta_backend->priv->connected_writable = -1; } /** * e_book_meta_backend_get_capabilities: * @meta_backend: an #EBookMetaBackend * * Returns: an #EBookBackend::capabilities property to be used by * the descendant in conjunction to the descendant's capabilities * in the result of e_book_backend_get_backend_property() with * #CLIENT_BACKEND_PROPERTY_CAPABILITIES. * * Since: 3.26 **/ const gchar * e_book_meta_backend_get_capabilities (EBookMetaBackend *meta_backend) { g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); return "refresh-supported" "," "bulk-adds" "," "bulk-modifies" "," "bulk-removes"; } /** * e_book_meta_backend_set_ever_connected: * @meta_backend: an #EBookMetaBackend * @value: value to set * * Sets whether the @meta_backend ever made a successful connection * to its destination. * * This is used by the @meta_backend itself, during the opening phase, * when it had not been connected yet, then it does so immediately, to * eventually report settings error easily. * * Since: 3.26 **/ void e_book_meta_backend_set_ever_connected (EBookMetaBackend *meta_backend, gboolean value) { EBookCache *book_cache; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); if ((value ? 1 : 0) == meta_backend->priv->ever_connected) return; book_cache = e_book_meta_backend_ref_cache (meta_backend); meta_backend->priv->ever_connected = value ? 1 : 0; e_cache_set_key_int (E_CACHE (book_cache), EBMB_KEY_EVER_CONNECTED, meta_backend->priv->ever_connected, NULL); g_clear_object (&book_cache); } /** * e_book_meta_backend_get_ever_connected: * @meta_backend: an #EBookMetaBackend * * Returns: Whether the @meta_backend ever made a successful connection * to its destination. * * Since: 3.26 **/ gboolean e_book_meta_backend_get_ever_connected (EBookMetaBackend *meta_backend) { gboolean result; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); if (meta_backend->priv->ever_connected == -1) { EBookCache *book_cache; book_cache = e_book_meta_backend_ref_cache (meta_backend); result = e_cache_get_key_int (E_CACHE (book_cache), EBMB_KEY_EVER_CONNECTED, NULL) == 1; g_clear_object (&book_cache); meta_backend->priv->ever_connected = result ? 1 : 0; } else { result = meta_backend->priv->ever_connected == 1; } return result; } /** * e_book_meta_backend_set_connected_writable: * @meta_backend: an #EBookMetaBackend * @value: value to set * * Sets whether the @meta_backend connected to a writable destination. * This value has meaning only if e_book_meta_backend_get_ever_connected() * is %TRUE. * * This is used by the @meta_backend itself, during the opening phase, * to set the backend writable or not also in the offline mode. * * Since: 3.26 **/ void e_book_meta_backend_set_connected_writable (EBookMetaBackend *meta_backend, gboolean value) { EBookCache *book_cache; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); if ((value ? 1 : 0) == meta_backend->priv->connected_writable) return; book_cache = e_book_meta_backend_ref_cache (meta_backend); meta_backend->priv->connected_writable = value ? 1 : 0; e_cache_set_key_int (E_CACHE (book_cache), EBMB_KEY_CONNECTED_WRITABLE, meta_backend->priv->connected_writable, NULL); g_clear_object (&book_cache); } /** * e_book_meta_backend_get_connected_writable: * @meta_backend: an #EBookMetaBackend * * This value has meaning only if e_book_meta_backend_get_ever_connected() * is %TRUE. * * Returns: Whether the @meta_backend connected to a writable destination. * * Since: 3.26 **/ gboolean e_book_meta_backend_get_connected_writable (EBookMetaBackend *meta_backend) { gboolean result; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); if (meta_backend->priv->connected_writable == -1) { EBookCache *book_cache; book_cache = e_book_meta_backend_ref_cache (meta_backend); result = e_cache_get_key_int (E_CACHE (book_cache), EBMB_KEY_CONNECTED_WRITABLE, NULL) == 1; g_clear_object (&book_cache); meta_backend->priv->connected_writable = result ? 1 : 0; } else { result = meta_backend->priv->connected_writable == 1; } return result; } /** * e_book_meta_backend_dup_sync_tag: * @meta_backend: an #EBookMetaBackend * * Returns the last known synchronization tag, the same as used to * call e_book_meta_backend_get_changes_sync(). * * Free the returned string with g_free(), when no longer needed. * * Returns: (transfer full) (nullable): The last known synchronization tag, * or %NULL, when none is stored. * * Since: 3.28 **/ gchar * e_book_meta_backend_dup_sync_tag (EBookMetaBackend *meta_backend) { EBookCache *book_cache; gchar *sync_tag; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); book_cache = e_book_meta_backend_ref_cache (meta_backend); if (!book_cache) return NULL; sync_tag = e_cache_dup_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, NULL); if (sync_tag && !*sync_tag) { g_free (sync_tag); sync_tag = NULL; } g_clear_object (&book_cache); return sync_tag; } /** * e_book_meta_backend_set_sync_tag: * @meta_backend: an #EBookMetaBackend * @sync_tag: (nullable): a sync tag to set, or %NULL to unset the old one * * Sets the @sync_tag for the @meta_backend. * * Since: 3.50 **/ void e_book_meta_backend_set_sync_tag (EBookMetaBackend *meta_backend, const gchar *sync_tag) { EBookCache *book_cache; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); book_cache = e_book_meta_backend_ref_cache (meta_backend); if (!book_cache) return; e_cache_set_key (E_CACHE (book_cache), EBMB_KEY_SYNC_TAG, sync_tag, NULL); g_clear_object (&book_cache); } static void ebmb_cache_revision_changed_cb (ECache *cache, gpointer user_data) { EBookMetaBackend *meta_backend = user_data; gchar *revision; g_return_if_fail (E_IS_CACHE (cache)); g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); revision = e_cache_dup_revision (cache); if (revision) { e_book_backend_notify_property_changed (E_BOOK_BACKEND (meta_backend), E_BOOK_BACKEND_PROPERTY_REVISION, revision); g_free (revision); } } static void ebmb_cache_categories_changed_cb (EBookCache *book_cache, const gchar *categories, gpointer user_data) { EBookMetaBackend *meta_backend = user_data; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); e_book_backend_notify_property_changed (E_BOOK_BACKEND (meta_backend), E_BOOK_BACKEND_PROPERTY_CATEGORIES, categories); } /** * e_book_meta_backend_set_cache: * @meta_backend: an #EBookMetaBackend * @cache: an #EBookCache to use * * Sets the @cache as the cache to be used by the @meta_backend. * By default, a cache.db in EBookBackend::cache-dir is created * in the constructed method. This function can be used to override * the default. * * Note the @meta_backend adds its own reference to the @cache. * * Since: 3.26 **/ void e_book_meta_backend_set_cache (EBookMetaBackend *meta_backend, EBookCache *cache) { g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_return_if_fail (E_IS_BOOK_CACHE (cache)); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->cache == cache) { g_mutex_unlock (&meta_backend->priv->property_lock); return; } g_clear_error (&meta_backend->priv->create_cache_error); if (meta_backend->priv->cache) { g_signal_handler_disconnect (meta_backend->priv->cache, meta_backend->priv->revision_changed_id); g_signal_handler_disconnect (meta_backend->priv->cache, meta_backend->priv->categories_changed_id); } g_clear_object (&meta_backend->priv->cache); meta_backend->priv->cache = g_object_ref (cache); meta_backend->priv->revision_changed_id = g_signal_connect_object (meta_backend->priv->cache, "revision-changed", G_CALLBACK (ebmb_cache_revision_changed_cb), meta_backend, 0); meta_backend->priv->categories_changed_id = g_signal_connect_object (meta_backend->priv->cache, "categories-changed", G_CALLBACK (ebmb_cache_categories_changed_cb), meta_backend, 0); g_mutex_unlock (&meta_backend->priv->property_lock); g_object_notify (G_OBJECT (meta_backend), "cache"); } /** * e_book_meta_backend_ref_cache: * @meta_backend: an #EBookMetaBackend * * Returns: (transfer full): Referenced #EBookCache, which is used by @meta_backend. * Unref it with g_object_unref(), when no longer needed. * * Since: 3.26 **/ EBookCache * e_book_meta_backend_ref_cache (EBookMetaBackend *meta_backend) { EBookCache *cache; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->cache) cache = g_object_ref (meta_backend->priv->cache); else cache = NULL; g_mutex_unlock (&meta_backend->priv->property_lock); return cache; } static gchar * ebmb_get_mime_type (const gchar *url, const gchar *content, gsize content_len) { gchar *content_type, *filename = NULL, *mime_type = NULL; if (url) { filename = g_filename_from_uri (url, NULL, NULL); if (filename) { gchar *extension; /* When storing inline attachments to the local file, the file extension is the mime type as stored in the attribute */ extension = strrchr (filename, '.'); if (extension) extension++; if (extension) { mime_type = g_uri_unescape_string (extension, NULL); if (mime_type && !strchr (mime_type, '/')) { gchar *tmp; tmp = g_strconcat ("image/", mime_type, NULL); g_free (mime_type); mime_type = tmp; } content_type = g_content_type_from_mime_type (mime_type); if (!content_type) { g_free (mime_type); mime_type = NULL; } g_free (content_type); } } } if (!mime_type) { content_type = g_content_type_guess (filename, (const guchar *) content, content_len, NULL); if (content_type) mime_type = g_content_type_get_mime_type (content_type); g_free (content_type); } g_free (filename); return mime_type; } /** * e_book_meta_backend_inline_local_photos_sync: * @meta_backend: an #EBookMetaBackend * @contact: an #EContact to work with * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Changes all URL photos and logos which point to a local file in @contact * to inline type, aka adds the file content into the @contact. * This is called automatically before e_book_meta_backend_save_contact_sync(). * * The reverse operation is e_book_meta_backend_store_inline_photos_sync(). * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_inline_local_photos_sync (EBookMetaBackend *meta_backend, EContact *contact, GCancellable *cancellable, GError **error) { GList *attributes, *link; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (E_IS_CONTACT (contact), FALSE); attributes = e_vcard_get_attributes (E_VCARD (contact)); for (link = attributes; link; link = g_list_next (link)) { EVCardAttribute *attr = link->data; const gchar *attr_name; GList *values; attr_name = e_vcard_attribute_get_name (attr); if (!attr_name || ( g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 && g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) { continue; } values = e_vcard_attribute_get_param (attr, EVC_VALUE); if (values && g_ascii_strcasecmp (values->data, "uri") == 0) { gchar *url; url = e_vcard_attribute_get_value (attr); if (url && g_str_has_prefix (url, LOCAL_PREFIX)) { GFile *file; gchar *basename; gchar *content; gsize len; file = g_file_new_for_uri (url); basename = g_file_get_basename (file); if (g_file_load_contents (file, cancellable, &content, &len, NULL, error)) { gchar *mime_type; const gchar *image_type, *pp; mime_type = ebmb_get_mime_type (url, content, len); if (mime_type && (pp = strchr (mime_type, '/'))) { image_type = pp + 1; } else { image_type = "X-EVOLUTION-UNKNOWN"; } e_vcard_attribute_remove_param (attr, EVC_TYPE); e_vcard_attribute_remove_param (attr, EVC_ENCODING); e_vcard_attribute_remove_param (attr, EVC_VALUE); e_vcard_attribute_remove_values (attr); e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE), image_type); e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_ENCODING), "b"); e_vcard_attribute_add_value_decoded (attr, content, len); g_free (mime_type); g_free (content); } else { success = FALSE; } g_object_unref (file); g_free (basename); } g_free (url); } } return success; } static gchar * ebmb_create_photo_local_filename (EBookMetaBackend *meta_backend, const gchar *uid, const gchar *attr_name, gint fileindex, const gchar *type) { EBookCache *book_cache; gchar *local_filename, *cache_path, *checksum, *prefix, *extension, *filename; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (attr_name != NULL, NULL); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, NULL); cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache))); checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1, uid, -1); prefix = g_strdup_printf ("%s-%s-%d", attr_name, checksum, fileindex); if (type && *type) extension = g_uri_escape_string (type, NULL, TRUE); else extension = NULL; filename = g_strconcat (prefix, extension ? "." : NULL, extension, NULL); local_filename = g_build_filename (cache_path, filename, NULL); g_object_unref (book_cache); g_free (cache_path); g_free (checksum); g_free (prefix); g_free (extension); g_free (filename); return local_filename; } /** * e_book_meta_backend_store_inline_photos_sync: * @meta_backend: an #EBookMetaBackend * @contact: an #EContact to work with * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Changes all inline photos and logos to URL type in @contact, which * will point to a local file instead, beside the cache file. * This is called automatically after e_book_meta_backend_load_contact_sync(). * * The reverse operation is e_book_meta_backend_inline_local_photos_sync(). * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_store_inline_photos_sync (EBookMetaBackend *meta_backend, EContact *contact, GCancellable *cancellable, GError **error) { gint fileindex; GList *attributes, *link; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (E_IS_CONTACT (contact), FALSE); attributes = e_vcard_get_attributes (E_VCARD (contact)); for (link = attributes, fileindex = 0; link; link = g_list_next (link), fileindex++) { EVCardAttribute *attr = link->data; const gchar *attr_name; GList *values; attr_name = e_vcard_attribute_get_name (attr); if (!attr_name || ( g_ascii_strcasecmp (attr_name, EVC_PHOTO) != 0 && g_ascii_strcasecmp (attr_name, EVC_LOGO) != 0)) { continue; } values = e_vcard_attribute_get_param (attr, EVC_ENCODING); if (values && (g_ascii_strcasecmp (values->data, "b") == 0 || g_ascii_strcasecmp (values->data, "base64") == 0)) { values = e_vcard_attribute_get_values_decoded (attr); if (values && values->data) { const GString *decoded = values->data; gchar *local_filename; if (!decoded->len) continue; values = e_vcard_attribute_get_param (attr, EVC_TYPE); local_filename = ebmb_create_photo_local_filename (meta_backend, e_contact_get_const (contact, E_CONTACT_UID), attr_name, fileindex, values ? values->data : NULL); if (local_filename && g_file_set_contents (local_filename, decoded->str, decoded->len, error)) { gchar *url; e_vcard_attribute_remove_param (attr, EVC_TYPE); e_vcard_attribute_remove_param (attr, EVC_ENCODING); e_vcard_attribute_remove_param (attr, EVC_VALUE); e_vcard_attribute_remove_values (attr); url = g_filename_to_uri (local_filename, NULL, NULL); e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_VALUE), "uri"); e_vcard_attribute_add_value (attr, url); g_free (url); } else { success = FALSE; } g_free (local_filename); } } } return success; } /** * e_book_meta_backend_empty_cache_sync: * @meta_backend: an #EBookMetaBackend * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Empties the local cache by removing all known contacts from it * and notifies about such removal any opened views. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_empty_cache_sync (EBookMetaBackend *meta_backend, GCancellable *cancellable, GError **error) { EBookBackend *book_backend; EBookCache *book_cache; GSList *uids = NULL, *link; gchar *cache_path, *cache_filename; gboolean success; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); e_cache_lock (E_CACHE (book_cache), E_CACHE_LOCK_WRITE); book_backend = E_BOOK_BACKEND (meta_backend); success = e_book_cache_search_uids (book_cache, NULL, &uids, cancellable, error); if (success) success = e_cache_remove_all (E_CACHE (book_cache), cancellable, error); e_cache_unlock (E_CACHE (book_cache), success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK); cache_path = g_path_get_dirname (e_cache_get_filename (E_CACHE (book_cache))); cache_filename = g_path_get_basename (e_cache_get_filename (E_CACHE (book_cache))); g_object_unref (book_cache); if (success) { GDir *dir; for (link = uids; link; link = g_slist_next (link)) { const gchar *uid = link->data; if (!uid) continue; e_book_backend_notify_remove (book_backend, uid); } g_mutex_lock (&meta_backend->priv->property_lock); for (link = meta_backend->priv->cursors; link; link = g_slist_next (link)) { EDataBookCursor *cursor = link->data; e_data_book_cursor_recalculate (cursor, cancellable, NULL); } g_mutex_unlock (&meta_backend->priv->property_lock); /* Remove also all photos and logos stored beside the cache */ dir = g_dir_open (cache_path, 0, NULL); if (dir) { const gchar *filename; while (filename = g_dir_read_name (dir), filename) { if ((g_str_has_prefix (filename, EVC_PHOTO) || g_str_has_prefix (filename, EVC_LOGO)) && g_strcmp0 (cache_filename, filename) != 0) { if (g_unlink (filename) == -1) { /* Something failed, ignore the error */ } } } g_dir_close (dir); } } g_slist_free_full (uids, g_free); g_free (cache_filename); g_free (cache_path); return success; } /** * e_book_meta_backend_schedule_refresh: * @meta_backend: an #EBookMetaBackend * * Schedules refresh of the content of the @meta_backend. If there's any * already scheduled, then the function does nothing. * * Use e_book_meta_backend_refresh_sync() to refresh the @meta_backend * immediately. * * Since: 3.26 **/ void e_book_meta_backend_schedule_refresh (EBookMetaBackend *meta_backend) { GCancellable *cancellable; g_return_if_fail (E_IS_BOOK_META_BACKEND (meta_backend)); g_mutex_lock (&meta_backend->priv->property_lock); if (meta_backend->priv->refresh_cancellable) { /* Already refreshing the content */ g_mutex_unlock (&meta_backend->priv->property_lock); return; } cancellable = g_cancellable_new (); meta_backend->priv->refresh_cancellable = g_object_ref (cancellable); g_mutex_unlock (&meta_backend->priv->property_lock); e_book_backend_schedule_custom_operation (E_BOOK_BACKEND (meta_backend), cancellable, ebmb_refresh_thread_func, NULL, NULL); g_object_unref (cancellable); } /** * e_book_meta_backend_refresh_sync: * @meta_backend: an #EBookMetaBackend * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Refreshes the @meta_backend immediately. To just schedule refresh * operation call e_book_meta_backend_schedule_refresh(). * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_refresh_sync (EBookMetaBackend *meta_backend, GCancellable *cancellable, GError **error) { g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); return ebmb_refresh_internal_sync (meta_backend, TRUE, cancellable, error); } /** * e_book_meta_backend_ensure_connected_sync: * @meta_backend: an #EBookMetaBackend * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Ensures that the @meta_backend is connected to its destination. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_ensure_connected_sync (EBookMetaBackend *meta_backend, GCancellable *cancellable, GError **error) { EBackend *backend; ENamedParameters *credentials; ESource *source; ESourceAuthenticationResult auth_result = E_SOURCE_AUTHENTICATION_UNKNOWN; ESourceCredentialsReason creds_reason = E_SOURCE_CREDENTIALS_REASON_ERROR; gchar *certificate_pem = NULL; GTlsCertificateFlags certificate_errors = 0; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); backend = E_BACKEND (meta_backend); if (!e_backend_get_online (backend) && e_backend_is_destination_reachable (backend, cancellable, NULL)) e_backend_set_online (backend, TRUE); if (!e_backend_get_online (backend)) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_REPOSITORY_OFFLINE, NULL)); return FALSE; } g_mutex_lock (&meta_backend->priv->property_lock); credentials = e_named_parameters_new_clone (meta_backend->priv->last_credentials); g_mutex_unlock (&meta_backend->priv->property_lock); g_mutex_lock (&meta_backend->priv->connect_lock); source = e_backend_get_source (backend); if (e_source_get_connection_status (source) != E_SOURCE_CONNECTION_STATUS_CONNECTED) e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING); if (e_book_meta_backend_connect_sync (meta_backend, credentials, &auth_result, &certificate_pem, &certificate_errors, cancellable, &local_error)) { ebmb_update_connection_values (meta_backend); e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED); g_mutex_unlock (&meta_backend->priv->connect_lock); e_named_parameters_free (credentials); return TRUE; } e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); g_mutex_unlock (&meta_backend->priv->connect_lock); e_named_parameters_free (credentials); g_warn_if_fail (auth_result != E_SOURCE_AUTHENTICATION_ACCEPTED); if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND)) { e_backend_set_online (backend, FALSE); g_propagate_error (error, local_error); g_free (certificate_pem); return FALSE; } switch (auth_result) { case E_SOURCE_AUTHENTICATION_UNKNOWN: if (local_error) g_propagate_error (error, local_error); g_free (certificate_pem); return FALSE; case E_SOURCE_AUTHENTICATION_ERROR: creds_reason = E_SOURCE_CREDENTIALS_REASON_ERROR; break; case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED: creds_reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED; break; case E_SOURCE_AUTHENTICATION_ACCEPTED: g_warn_if_reached (); break; case E_SOURCE_AUTHENTICATION_REJECTED: creds_reason = E_SOURCE_CREDENTIALS_REASON_REJECTED; break; case E_SOURCE_AUTHENTICATION_REQUIRED: creds_reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED; break; } e_backend_schedule_credentials_required (backend, creds_reason, certificate_pem, certificate_errors, local_error, cancellable, G_STRFUNC); g_clear_error (&local_error); g_free (certificate_pem); return FALSE; } /** * e_book_meta_backend_split_changes_sync: * @meta_backend: an #EBookMetaBackend * @objects: (inout) (element-type EBookMetaBackendInfo): * a #GSList of #EBookMetaBackendInfo object infos to split * @out_created_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which had been created * @out_modified_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which had been modified * @out_removed_objects: (out) (element-type EBookMetaBackendInfo) (transfer full) (nullable): * a #GSList of #EBookMetaBackendInfo object infos which had been removed; * it can be %NULL, to not gather list of removed object infos * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Splits @objects into created/modified/removed lists according to current local * cache content. Only the @out_removed_objects can be %NULL, others cannot. * The function modifies @objects by moving its 'data' to corresponding out * lists and sets the @objects 'data' to %NULL. * * Each output #GSList should be freed with * g_slist_free_full (objects, e_book_meta_backend_info_free); * when no longer needed. * * The caller is still responsible to free @objects as well. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { GHashTable *locally_cached; /* EContactId * ~> gchar *revision */ GHashTableIter iter; GSList *link; EBookCache *book_cache; gpointer key, value; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_created_objects, FALSE); g_return_val_if_fail (out_modified_objects, FALSE); *out_created_objects = NULL; *out_modified_objects = NULL; if (out_removed_objects) *out_removed_objects = NULL; book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); locally_cached = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (!e_book_cache_search_with_callback (book_cache, NULL, ebmb_gather_locally_cached_objects_cb, locally_cached, cancellable, error)) { g_hash_table_destroy (locally_cached); g_object_unref (book_cache); return FALSE; } for (link = objects; link; link = g_slist_next (link)) { EBookMetaBackendInfo *nfo = link->data; if (!nfo) continue; if (!g_hash_table_contains (locally_cached, nfo->uid)) { link->data = NULL; *out_created_objects = g_slist_prepend (*out_created_objects, nfo); } else { const gchar *local_revision = g_hash_table_lookup (locally_cached, nfo->uid); if (g_strcmp0 (local_revision, nfo->revision) != 0) { link->data = NULL; *out_modified_objects = g_slist_prepend (*out_modified_objects, nfo); } g_hash_table_remove (locally_cached, nfo->uid); } } if (out_removed_objects) { /* What left in the hash table is removed from the remote side */ g_hash_table_iter_init (&iter, locally_cached); while (g_hash_table_iter_next (&iter, &key, &value)) { const gchar *uid = key; const gchar *revision = value; EBookMetaBackendInfo *nfo; if (!uid) { g_warn_if_reached (); continue; } nfo = e_book_meta_backend_info_new (uid, revision, NULL, NULL); *out_removed_objects = g_slist_prepend (*out_removed_objects, nfo); } *out_removed_objects = g_slist_reverse (*out_removed_objects); } g_hash_table_destroy (locally_cached); g_object_unref (book_cache); *out_created_objects = g_slist_reverse (*out_created_objects); *out_modified_objects = g_slist_reverse (*out_modified_objects); return TRUE; } /** * e_book_meta_backend_process_changes_sync: * @meta_backend: an #EBookMetaBackend * @created_objects: (element-type EBookMetaBackendInfo) (nullable): * a #GSList of #EBookMetaBackendInfo object infos which had been created * @modified_objects: (element-type EBookMetaBackendInfo) (nullable): * a #GSList of #EBookMetaBackendInfo object infos which had been modified * @removed_objects: (element-type EBookMetaBackendInfo) (nullable): * a #GSList of #EBookMetaBackendInfo object infos which had been removed * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Processes given changes by updating local cache content accordingly. * The @meta_backend processes the changes like being online and particularly * requires to be online to load created and modified objects when needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { EBookCache *book_cache; GHashTable *covered_uids; GString *invalid_objects = NULL; GSList *link; gboolean success = TRUE; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); book_cache = e_book_meta_backend_ref_cache (meta_backend); g_return_val_if_fail (book_cache != NULL, FALSE); covered_uids = g_hash_table_new (g_str_hash, g_str_equal); /* Removed objects first */ for (link = (GSList *) removed_objects; link && success; link = g_slist_next (link)) { EBookMetaBackendInfo *nfo = link->data; if (!nfo) { g_warn_if_reached (); continue; } success = ebmb_maybe_remove_from_cache (meta_backend, book_cache, E_CACHE_IS_ONLINE, nfo->uid, 0, cancellable, error); } /* Then modified objects */ for (link = (GSList *) modified_objects; link && success; link = g_slist_next (link)) { EBookMetaBackendInfo *nfo = link->data; GError *local_error = NULL; if (!nfo || !nfo->uid || !*nfo->uid || g_hash_table_contains (covered_uids, nfo->uid)) continue; g_hash_table_insert (covered_uids, nfo->uid, NULL); success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error); /* Do not stop on invalid objects, just notify about them later, and load as many as possible */ if (!success && g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG)) { if (!invalid_objects) { invalid_objects = g_string_new (local_error->message); } else { g_string_append_c (invalid_objects, '\n'); g_string_append (invalid_objects, local_error->message); } g_clear_error (&local_error); success = TRUE; } else if (local_error) { g_propagate_error (error, local_error); } } g_hash_table_remove_all (covered_uids); /* Finally created objects */ for (link = (GSList *) created_objects; link && success; link = g_slist_next (link)) { EBookMetaBackendInfo *nfo = link->data; GError *local_error = NULL; if (!nfo || !nfo->uid || !*nfo->uid) continue; success = ebmb_load_contact_wrapper_sync (meta_backend, book_cache, nfo->uid, nfo->object, nfo->extra, NULL, cancellable, &local_error); /* Do not stop on invalid objects, just notify about them later, and load as many as possible */ if (!success && g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG)) { if (!invalid_objects) { invalid_objects = g_string_new (local_error->message); } else { g_string_append_c (invalid_objects, '\n'); g_string_append (invalid_objects, local_error->message); } g_clear_error (&local_error); success = TRUE; } else if (local_error) { g_propagate_error (error, local_error); } } g_hash_table_destroy (covered_uids); if (invalid_objects) { e_book_backend_notify_error (E_BOOK_BACKEND (meta_backend), invalid_objects->str); g_string_free (invalid_objects, TRUE); } g_clear_object (&book_cache); return success; } /** * e_book_meta_backend_connect_sync: * @meta_backend: an #EBookMetaBackend * @credentials: (nullable): an #ENamedParameters with previously used credentials, or %NULL * @out_auth_result: (out): an #ESourceAuthenticationResult with an authentication result * @out_certificate_pem: (out) (transfer full): a PEM encoded certificate on failure, or %NULL * @out_certificate_errors: (out): a #GTlsCertificateFlags on failure, or 0 * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * This is called always before any operation which requires a connection * to the remote side. It can fail with an #E_CLIENT_ERROR_REPOSITORY_OFFLINE * error to indicate that the remote side cannot be currently reached. Other * errors are propagated to the caller/client side. This method is not called * when the backend is offline. * * The descendant should also call e_book_backend_set_writable() after successful * connect to the remote side. This value is stored for later use, when being * opened offline. * * The @credentials parameter consists of the previously used credentials. * It's always %NULL with the first connection attempt. To get the credentials, * just set the @out_auth_result to %E_SOURCE_AUTHENTICATION_REQUIRED for * the first time and the function will be called again once the credentials * are available. See the documentation of #ESourceAuthenticationResult for * other available results. * * The out parameters are passed to e_backend_schedule_credentials_required() * and are ignored when the descendant returns %TRUE, aka they are used * only if the connection fails. The @out_certificate_pem and @out_certificate_errors * should be used together and they can be left untouched if the failure reason was * not related to certificate. Use @out_auth_result %E_SOURCE_AUTHENTICATION_UNKNOWN * to indicate other error than @credentials error, otherwise the @error is used * according to @out_auth_result value. * * It is mandatory to implement this virtual method by the descendant. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->connect_sync != NULL, FALSE); return klass->connect_sync (meta_backend, credentials, out_auth_result, out_certificate_pem, out_certificate_errors, cancellable, error); } /** * e_book_meta_backend_disconnect_sync: * @meta_backend: an #EBookMetaBackend * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * This is called when the backend goes into offline mode or * when the disconnect is required. The implementation should * not report any error when it is called and the @meta_backend * is not connected. * * It is mandatory to implement this virtual method by the descendant. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_disconnect_sync (EBookMetaBackend *meta_backend, GCancellable *cancellable, GError **error) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->disconnect_sync != NULL, FALSE); return klass->disconnect_sync (meta_backend, cancellable, error); } /** * e_book_meta_backend_get_changes_sync: * @meta_backend: an #EBookMetaBackend * @last_sync_tag: (nullable): optional sync tag from the last check * @is_repeat: set to %TRUE when this is the repeated call * @out_new_sync_tag: (out) (transfer full): new sync tag to store on success * @out_repeat: (out): whether to repeat this call again; default is %FALSE * @out_created_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which had been created since * the last check * @out_modified_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which had been modified since * the last check * @out_removed_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which had been removed since * the last check * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Gathers the changes since the last check which had been done * on the remote side. * * The @last_sync_tag can be used as a tag of the last check. This can be %NULL, * when there was no previous call or when the descendant doesn't store any * such tags. The @out_new_sync_tag can be populated with a value to be stored * and used the next time. * * The @out_repeat can be set to %TRUE when the descendant didn't finish * read of all the changes. In that case the @meta_backend calls this * function again with the @out_new_sync_tag as the @last_sync_tag, but also * notifies about the found changes immediately. The @is_repeat is set * to %TRUE as well in this case, otherwise it's %FALSE. * * The descendant can populate also EBookMetaBackendInfo::object of * the @out_created_objects and @out_modified_objects, if known, in which * case this will be used instead of loading it with e_book_meta_backend_load_contact_sync(). * * It is optional to implement this virtual method by the descendant. * The default implementation calls e_book_meta_backend_list_existing_sync() * and then compares the list with the current content of the local cache * and populates the respective lists appropriately. * * Each output #GSList should be freed with * g_slist_free_full (objects, e_book_meta_backend_info_free); * when no longer needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { EBookMetaBackendClass *klass; gint repeat_count = 0; gboolean success = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_new_sync_tag != NULL, FALSE); g_return_val_if_fail (out_repeat != NULL, FALSE); g_return_val_if_fail (out_created_objects != NULL, FALSE); g_return_val_if_fail (out_created_objects != NULL, FALSE); g_return_val_if_fail (out_modified_objects != NULL, FALSE); g_return_val_if_fail (out_removed_objects != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->get_changes_sync != NULL, FALSE); while (!success && repeat_count <= MAX_REPEAT_COUNT) { guint wait_credentials_stamp; g_mutex_lock (&meta_backend->priv->wait_credentials_lock); wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp; g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); repeat_count++; success = klass->get_changes_sync (meta_backend, last_sync_tag, is_repeat, out_new_sync_tag, out_repeat, out_created_objects, out_modified_objects, out_removed_objects, cancellable, &local_error); if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable)) break; } if (local_error) g_propagate_error (error, local_error); return success; } /** * e_book_meta_backend_list_existing_sync: * @meta_backend: an #EBookMetaBackend * @out_new_sync_tag: (out) (transfer full): optional return location for a new sync tag * @out_existing_objects: (out) (element-type EBookMetaBackendInfo) (transfer full): * a #GSList of #EBookMetaBackendInfo object infos which are stored on the remote side * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Used to get list of all existing objects on the remote side. The descendant * can optionally provide @out_new_sync_tag, which will be stored on success, if * not %NULL. The descendant can populate also EBookMetaBackendInfo::object of * the @out_existing_objects, if known, in which case this will be used instead * of loading it with e_book_meta_backend_load_contact_sync(). * * It is mandatory to implement this virtual method by the descendant, unless * it implements its own #EBookMetaBackendClass.get_changes_sync(). * * The @out_existing_objects #GSList should be freed with * g_slist_free_full (objects, e_book_meta_backend_info_free); * when no longer needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_list_existing_sync (EBookMetaBackend *meta_backend, gchar **out_new_sync_tag, GSList **out_existing_objects, GCancellable *cancellable, GError **error) { EBookMetaBackendClass *klass; gint repeat_count = 0; gboolean success = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_existing_objects != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->list_existing_sync != NULL, FALSE); while (!success && repeat_count <= MAX_REPEAT_COUNT) { guint wait_credentials_stamp; g_mutex_lock (&meta_backend->priv->wait_credentials_lock); wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp; g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); repeat_count++; success = klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, cancellable, &local_error); if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable)) break; } if (local_error) g_propagate_error (error, local_error); return success; } /** * e_book_meta_backend_load_contact_sync: * @meta_backend: an #EBookMetaBackend * @uid: a contact UID * @extra: (nullable): optional extra data stored with the contact, or %NULL * @out_contact: (out) (transfer full): a loaded contact, as an #EContact * @out_extra: (out) (transfer full): an extra data to store to #EBookCache with this contact * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Loads a contact from the remote side. * * It is mandatory to implement this virtual method by the descendant. * * The returned @out_contact should be freed with g_object_unref(), * when no longer needed. * * The returned @out_extra should be freed with g_free(), when no longer * needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { EBookMetaBackendClass *klass; gint repeat_count = 0; gboolean success = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (uid != NULL, FALSE); g_return_val_if_fail (out_contact != NULL, FALSE); g_return_val_if_fail (out_extra != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->load_contact_sync != NULL, FALSE); while (!success && repeat_count <= MAX_REPEAT_COUNT) { guint wait_credentials_stamp; g_mutex_lock (&meta_backend->priv->wait_credentials_lock); wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp; g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); repeat_count++; success = klass->load_contact_sync (meta_backend, uid, extra, out_contact, out_extra, cancellable, &local_error); if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable)) break; } if (local_error) g_propagate_error (error, local_error); return success; } /** * e_book_meta_backend_save_contact_sync: * @meta_backend: an #EBookMetaBackend * @overwrite_existing: %TRUE when can overwrite existing contacts, %FALSE otherwise * @conflict_resolution: one of #EConflictResolution, what to do on conflicts * @contact: an #EContact to save * @extra: (nullable): extra data saved with the contacts in an #EBookCache * @opflags: bit-or of EBookOperationFlags * @out_new_uid: (out) (transfer full): return location for the UID of the saved contact * @out_new_extra: (out) (transfer full): return location for the extra data to store with the contact * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Saves one contact into the remote side. When the @overwrite_existing is %TRUE, then * the descendant can overwrite an object with the same UID on the remote side * (usually used for modify). The @conflict_resolution defines what to do when * the remote side had made any changes to the object since the last update. * * The @contact has already converted locally stored photos and logos * into inline variants, thus it's not needed to call * e_book_meta_backend_inline_local_photos_sync() by the descendant. * * The @out_new_uid can be populated with a UID of the saved contact as the server * assigned it to it. This UID, if set, is loaded from the remote side afterwards, * also to see whether any changes had been made to the contact by the remote side. * * The @out_new_extra can be populated with a new extra data to save with the contact. * Left it %NULL, to keep the same value as the @extra. * * The descendant can use an #E_CLIENT_ERROR_OUT_OF_SYNC error to indicate that * the save failed due to made changes on the remote side, and let the @meta_backend * resolve this conflict based on the @conflict_resolution on its own. * The #E_CLIENT_ERROR_OUT_OF_SYNC error should not be used when the descendant * is able to resolve the conflicts itself. * * It is mandatory to implement this virtual method by the writable descendant. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_save_contact_sync (EBookMetaBackend *meta_backend, gboolean overwrite_existing, EConflictResolution conflict_resolution, /* const */ EContact *contact, const gchar *extra, guint32 opflags, gchar **out_new_uid, gchar **out_new_extra, GCancellable *cancellable, GError **error) { EBookMetaBackendClass *klass; gint repeat_count = 0; gboolean success = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (E_IS_CONTACT (contact), FALSE); g_return_val_if_fail (out_new_uid != NULL, FALSE); g_return_val_if_fail (out_new_extra != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); if (!klass->save_contact_sync) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_NOT_SUPPORTED, NULL)); return FALSE; } while (!success && repeat_count <= MAX_REPEAT_COUNT) { guint wait_credentials_stamp; g_mutex_lock (&meta_backend->priv->wait_credentials_lock); wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp; g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); repeat_count++; success = klass->save_contact_sync (meta_backend, overwrite_existing, conflict_resolution, contact, extra, opflags, out_new_uid, out_new_extra, cancellable, &local_error); if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable)) break; } if (local_error) g_propagate_error (error, local_error); return success; } /** * e_book_meta_backend_remove_contact_sync: * @meta_backend: an #EBookMetaBackend * @conflict_resolution: an #EConflictResolution to use * @uid: a contact UID * @extra: (nullable): extra data being saved with the contact in the local cache, or %NULL * @object: (nullable): corresponding vCard object, as stored in the local cache, or %NULL * @opflags: bit-or of #EBookOperationFlags * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Removes a contact from the remote side. The @object is not %NULL when * it's removing locally deleted object in offline mode. Being it %NULL, * the descendant can obtain the object from the #EBookCache. * * It is mandatory to implement this virtual method by the writable descendant. * * Returns: Whether succeeded. * * Since: 3.26 **/ 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) { EBookMetaBackendClass *klass; gint repeat_count = 0; gboolean success = FALSE; GError *local_error = NULL; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (uid != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); if (!klass->remove_contact_sync) { g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_NOT_SUPPORTED, NULL)); return FALSE; } while (!success && repeat_count <= MAX_REPEAT_COUNT) { guint wait_credentials_stamp; g_mutex_lock (&meta_backend->priv->wait_credentials_lock); wait_credentials_stamp = meta_backend->priv->wait_credentials_stamp; g_mutex_unlock (&meta_backend->priv->wait_credentials_lock); g_clear_error (&local_error); repeat_count++; success = klass->remove_contact_sync (meta_backend, conflict_resolution, uid, extra, object, opflags, cancellable, &local_error); if (!success && repeat_count <= MAX_REPEAT_COUNT && !ebmb_maybe_wait_for_credentials (meta_backend, wait_credentials_stamp, local_error, cancellable)) break; } if (local_error) g_propagate_error (error, local_error); return success; } /** * e_book_meta_backend_search_sync: * @meta_backend: an #EBookMetaBackend * @expr: (nullable): a search expression, or %NULL * @meta_contact: %TRUE, when return #EContact filled with UID and REV only, %FALSE to return full contacts * @out_contacts: (out) (transfer full) (element-type EContact): return location for the found contacts as #EContact * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Searches @meta_backend with given expression @expr and returns * found contacts as a #GSList of #EContact @out_contacts. * Free the returned @out_contacts with g_slist_free_full (contacts, g_object_unref); * when no longer needed. * When the @expr is %NULL, all objects are returned. To get * UID-s instead, call e_book_meta_backend_search_uids_sync(). * * It is optional to implement this virtual method by the descendant. * The default implementation searches @meta_backend's cache. It's also * not required to be online for searching, thus @meta_backend doesn't * ensure it. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_search_sync (EBookMetaBackend *meta_backend, const gchar *expr, gboolean meta_contact, GSList **out_contacts, GCancellable *cancellable, GError **error) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_contacts != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->search_sync != NULL, FALSE); return klass->search_sync (meta_backend, expr, meta_contact, out_contacts, cancellable, error); } /** * e_book_meta_backend_search_uids_sync: * @meta_backend: an #EBookMetaBackend * @expr: (nullable): a search expression, or %NULL * @out_uids: (out) (transfer full) (element-type utf8): return location for the found contact UID-s * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Searches @meta_backend with given expression @expr and returns * found contact UID-s as a #GSList @out_contacts. * Free the returned @out_uids with g_slist_free_full (uids, g_free); * when no longer needed. * When the @expr is %NULL, all UID-s are returned. To get #EContact(s) * instead, call e_book_meta_backend_search_sync(). * * It is optional to implement this virtual method by the descendant. * The default implementation searches @meta_backend's cache. It's also * not required to be online for searching, thus @meta_backend doesn't * ensure it. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_book_meta_backend_search_uids_sync (EBookMetaBackend *meta_backend, const gchar *expr, GSList **out_uids, GCancellable *cancellable, GError **error) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); g_return_val_if_fail (out_uids != NULL, FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->search_uids_sync != NULL, FALSE); return klass->search_uids_sync (meta_backend, expr, out_uids, cancellable, error); } /** * e_book_meta_backend_requires_reconnect: * @meta_backend: an #EBookMetaBackend * * Determines, whether current source content requires reconnect of the backend. * * It is optional to implement this virtual method by the descendant. The default * implementation compares %E_SOURCE_EXTENSION_AUTHENTICATION and * %E_SOURCE_EXTENSION_WEBDAV_BACKEND, if existing in the source, * with the values after the last successful connect and returns * %TRUE when they changed. It always return %TRUE when there was * no successful connect done yet. * * Returns: %TRUE, when reconnect is required, %FALSE otherwise. * * Since: 3.26 **/ gboolean e_book_meta_backend_requires_reconnect (EBookMetaBackend *meta_backend) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->requires_reconnect != NULL, FALSE); return klass->requires_reconnect (meta_backend); } /** * e_book_meta_backend_get_ssl_error_details: * @meta_backend: an #EBookMetaBackend * @out_certificate_pem: (out): SSL certificate encoded in PEM format * @out_certificate_errors: (out): bit-or of #GTlsCertificateFlags claiming the certificate errors * * It is optional to implement this virtual method by the descendants. * It is used to receive SSL error details when any online operation * returns E_CLIENT_ERROR, E_CLIENT_ERROR_TLS_NOT_AVAILABLE error. * * Returns: %TRUE, when the SSL error details had been available and * the out parameters populated, %FALSE otherwise. * * Since: 3.28 **/ gboolean e_book_meta_backend_get_ssl_error_details (EBookMetaBackend *meta_backend, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors) { EBookMetaBackendClass *klass; g_return_val_if_fail (E_IS_BOOK_META_BACKEND (meta_backend), FALSE); klass = E_BOOK_META_BACKEND_GET_CLASS (meta_backend); g_return_val_if_fail (klass != NULL, FALSE); g_return_val_if_fail (klass->get_ssl_error_details != NULL, FALSE); return klass->get_ssl_error_details (meta_backend, out_certificate_pem, out_certificate_errors); }