[evolution-ews] Bug 625149 - Support offline for books/calendars



commit aea7a676fcbaf19c725f96fd981326e24e02681a
Author: Milan Crha <mcrha redhat com>
Date:   Mon Jun 19 18:33:00 2017 +0200

    Bug 625149 - Support offline for books/calendars

 src/addressbook/e-book-backend-ews.c |  137 +++++++++++++++++++++++++++++++++-
 src/calendar/e-cal-backend-ews.c     |   90 ++++++++++++++++++++++-
 src/server/e-ews-connection.c        |    3 +-
 3 files changed, 227 insertions(+), 3 deletions(-)
---
diff --git a/src/addressbook/e-book-backend-ews.c b/src/addressbook/e-book-backend-ews.c
index c37a79d..205c9e4 100644
--- a/src/addressbook/e-book-backend-ews.c
+++ b/src/addressbook/e-book-backend-ews.c
@@ -56,6 +56,8 @@
 #define EDB_ERROR(_code) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, NULL)
 #define EDB_ERROR_EX(_code,_msg) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, _msg)
 
+#define X_EWS_ORIGINAL_VCARD "X-EWS-ORIGINAL-VCARD"
+
 #define EWS_MAX_FETCH_COUNT 500
 
 #define ELEMENT_TYPE_SIMPLE 0x01 /* simple string fields */
@@ -132,6 +134,9 @@ ebb_ews_convert_error_to_edb_error (GError **perror)
                case EWS_CONNECTION_ERROR_ITEMNOTFOUND:
                        error = EDB_ERROR_EX (CONTACT_NOT_FOUND, (*perror)->message);
                        break;
+               case EWS_CONNECTION_ERROR_UNAVAILABLE:
+                       g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND, 
(*perror)->message);
+                       break;
                }
 
                if (!error)
@@ -1839,6 +1844,8 @@ ebb_ews_unset_connection (EBookBackendEws *bbews)
                }
        }
 
+       g_clear_object (&bbews->priv->cnc);
+
        g_rec_mutex_unlock (&bbews->priv->cnc_lock);
 }
 
@@ -2202,6 +2209,61 @@ ebb_ews_check_gal_changes (EBookBackendEws *bbews,
        return success;
 }
 
+static void
+ebb_ews_remove_original_vcard (EContact *contact)
+{
+       g_return_if_fail (E_IS_CONTACT (contact));
+
+       e_vcard_remove_attributes (E_VCARD (contact), NULL, X_EWS_ORIGINAL_VCARD);
+}
+
+static void
+ebb_ews_store_original_vcard (EContact *contact)
+{
+       EVCard *vcard;
+       EVCardAttribute *attr;
+       gchar *vcard_str;
+
+       g_return_if_fail (E_IS_CONTACT (contact));
+
+       ebb_ews_remove_original_vcard (contact);
+
+       vcard = E_VCARD (contact);
+
+       vcard_str = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30);
+
+       attr = e_vcard_attribute_new ("", X_EWS_ORIGINAL_VCARD);
+       e_vcard_attribute_add_value (attr, vcard_str);
+       e_vcard_add_attribute (vcard, attr);
+
+       g_free (vcard_str);
+}
+
+static const gchar *
+ebb_ews_get_original_vcard (EContact *contact)
+{
+       EVCardAttribute *attr;
+       GList *values = NULL;
+       const gchar *vcard;
+
+       g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+
+       attr = e_vcard_get_attribute (E_VCARD (contact), X_EWS_ORIGINAL_VCARD);
+       if (!attr)
+               return NULL;
+
+       values = e_vcard_attribute_get_values (attr);
+       if (!values)
+               return NULL;
+
+       vcard = values->data;
+
+       if (vcard && *vcard)
+               return vcard;
+
+       return NULL;
+}
+
 typedef struct {
        /* For future use */
        gpointer restriction;
@@ -2533,6 +2595,8 @@ ebb_ews_update_cache_for_expression (EBookBackendEws *bbews,
                                        }
                                }
 
+                               ebb_ews_store_original_vcard (contact);
+
                                nfo = e_book_meta_backend_info_new (e_contact_get_const (contact, 
E_CONTACT_UID),
                                        e_contact_get_const (contact, E_CONTACT_REV), NULL, NULL);
                                nfo->object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
@@ -2619,6 +2683,8 @@ ebb_ews_contacts_to_infos (const GSList *contacts) /* EContact * */
                if (!E_IS_CONTACT (contact))
                        continue;
 
+               ebb_ews_store_original_vcard (contact);
+
                nfo = e_book_meta_backend_info_new (
                        e_contact_get_const (contact, E_CONTACT_UID),
                        e_contact_get_const (contact, E_CONTACT_REV),
@@ -2996,9 +3062,12 @@ ebb_ews_load_contact_sync (EBookMetaBackend *meta_backend,
                GSList *contacts = NULL;
 
                success = ebb_ews_fetch_items_sync (bbews, items, &contacts, cancellable, error);
-               if (success && contacts)
+               if (success && contacts) {
                        *out_contact = g_object_ref (contacts->data);
 
+                       ebb_ews_store_original_vcard (*out_contact);
+               }
+
                g_slist_free_full (contacts, g_object_unref);
        }
 
@@ -3057,6 +3126,25 @@ ebb_ews_save_contact_sync (EBookMetaBackend *meta_backend,
 
                success = e_book_cache_get_contact (book_cache, e_contact_get_const (contact, E_CONTACT_UID), 
FALSE, &old_contact, cancellable, error);
                if (success) {
+                       const gchar *original_vcard;
+
+                       /* This is for offline changes, where the EContact in the cache
+                          is already modified, while the original, the one on the server,
+                          is different. Using the cached EContact in this case generates
+                          empty UpdateItem request and nothing is saved. */
+                       original_vcard = ebb_ews_get_original_vcard (old_contact);
+                       if (original_vcard) {
+                               EContact *tmp;
+
+                               tmp = e_contact_new_from_vcard (original_vcard);
+                               if (tmp) {
+                                       g_object_unref (old_contact);
+                                       old_contact = tmp;
+                               }
+                       }
+               }
+
+               if (success) {
                        ConvertData cd;
                        const gchar *conflict_res = "AlwaysOverwrite";
 
@@ -3271,6 +3359,49 @@ ebb_ews_get_backend_property (EBookBackend *book_backend,
        return E_BOOK_BACKEND_CLASS (e_book_backend_ews_parent_class)->get_backend_property (book_backend, 
prop_name);
 }
 
+static gboolean
+ebb_ews_get_destination_address (EBackend *backend,
+                                gchar **host,
+                                guint16 *port)
+{
+       CamelEwsSettings *ews_settings;
+       SoupURI *soup_uri;
+       gchar *host_url;
+       gboolean result = FALSE;
+
+       g_return_val_if_fail (port != NULL, FALSE);
+       g_return_val_if_fail (host != NULL, FALSE);
+
+       /* Sanity checking */
+       if (!e_book_backend_get_registry (E_BOOK_BACKEND (backend)) ||
+           !e_backend_get_source (backend))
+               return FALSE;
+
+       ews_settings = ebb_ews_get_collection_settings (E_BOOK_BACKEND_EWS (backend));
+       g_return_val_if_fail (ews_settings != NULL, FALSE);
+
+       host_url = camel_ews_settings_dup_hosturl (ews_settings);
+       g_return_val_if_fail (host_url != NULL, FALSE);
+
+       soup_uri = soup_uri_new (host_url);
+       if (soup_uri) {
+               *host = g_strdup (soup_uri_get_host (soup_uri));
+               *port = soup_uri_get_port (soup_uri);
+
+               result = *host && **host;
+               if (!result) {
+                       g_free (*host);
+                       *host = NULL;
+               }
+
+               soup_uri_free (soup_uri);
+       }
+
+       g_free (host_url);
+
+       return result;
+}
+
 static void
 e_book_backend_ews_constructed (GObject *object)
 {
@@ -3334,6 +3465,7 @@ static void
 e_book_backend_ews_class_init (EBookBackendEwsClass *klass)
 {
        GObjectClass *object_class;
+       EBackendClass *backend_class;
        EBookBackendClass *book_backend_class;
        EBookMetaBackendClass *book_meta_backend_class;
 
@@ -3354,6 +3486,9 @@ e_book_backend_ews_class_init (EBookBackendEwsClass *klass)
        book_backend_class = E_BOOK_BACKEND_CLASS (klass);
        book_backend_class->get_backend_property = ebb_ews_get_backend_property;
 
+       backend_class = E_BACKEND_CLASS (klass);
+       backend_class->get_destination_address = ebb_ews_get_destination_address;
+
        object_class = G_OBJECT_CLASS (klass);
        object_class->constructed = e_book_backend_ews_constructed;
        object_class->dispose = e_book_backend_ews_dispose;
diff --git a/src/calendar/e-cal-backend-ews.c b/src/calendar/e-cal-backend-ews.c
index 57495a6..7adac7b 100644
--- a/src/calendar/e-cal-backend-ews.c
+++ b/src/calendar/e-cal-backend-ews.c
@@ -73,6 +73,8 @@ struct _ECalBackendEwsPrivate {
        gchar *attachments_dir;
 };
 
+#define X_EWS_ORIGINAL_COMP "X-EWS-ORIGINAL-COMP"
+
 #define EWS_MAX_FETCH_COUNT 100
 
 #define GET_ITEMS_SYNC_PROPERTIES \
@@ -165,6 +167,9 @@ ecb_ews_convert_error_to_edc_error (GError **perror)
                case EWS_CONNECTION_ERROR_ITEMNOTFOUND:
                        error = EDC_ERROR_EX (ObjectNotFound, (*perror)->message);
                        break;
+               case EWS_CONNECTION_ERROR_UNAVAILABLE:
+                       g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND, 
(*perror)->message);
+                       break;
                }
 
                if (!error)
@@ -235,6 +240,8 @@ ecb_ews_unset_connection (ECalBackendEws *cbews)
                }
        }
 
+       g_clear_object (&cbews->priv->cnc);
+
        g_rec_mutex_unlock (&cbews->priv->cnc_lock);
 }
 
@@ -860,6 +867,65 @@ ecb_ews_item_to_component_sync (ECalBackendEws *cbews,
        return res_component;
 }
 
+static void
+ecb_ews_store_original_comp (ECalComponent *comp)
+{
+       gchar *comp_str;
+       gchar *base64;
+
+       g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+       comp_str = e_cal_component_get_as_string (comp);
+       g_return_if_fail (comp_str != NULL);
+
+       /* Include NUL-terminator */
+       base64 = g_base64_encode ((const guchar *) comp_str, strlen (comp_str) + 1);
+
+       e_cal_util_set_x_property (e_cal_component_get_icalcomponent (comp),
+               X_EWS_ORIGINAL_COMP, base64);
+
+       g_free (base64);
+       g_free (comp_str);
+}
+
+static ECalComponent * /* free with g_object_unref(), if not NULL */
+ecb_ews_restore_original_comp (ECalComponent *from_comp)
+{
+       ECalComponent *comp = NULL;
+       const gchar *original_base64;
+       guchar *decoded;
+       gsize len = -1;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (from_comp), NULL);
+
+       original_base64 = e_cal_util_get_x_property (e_cal_component_get_icalcomponent (from_comp), 
X_EWS_ORIGINAL_COMP);
+
+       if (!original_base64 || !*original_base64)
+               return NULL;
+
+       decoded = g_base64_decode (original_base64, &len);
+       if (!decoded || !*decoded || len <= 0) {
+               g_free (decoded);
+               return NULL;
+       }
+
+       if (decoded[len - 1] != '\0') {
+               gchar *tmp;
+
+               tmp = g_strndup ((const gchar *) decoded, len);
+
+               g_free (decoded);
+               decoded = (guchar *) tmp;
+       }
+
+       if (decoded && *decoded)
+               comp = e_cal_component_new_from_string ((const gchar *) decoded);
+
+       g_free (decoded);
+
+       return comp;
+}
+
 static gboolean
 ecb_ews_get_items_sync (ECalBackendEws *cbews,
                        const GSList *item_ids, /* gchar * */
@@ -953,6 +1019,8 @@ ecb_ews_get_items_sync (ECalBackendEws *cbews,
                                break;
                        }
 
+                       ecb_ews_store_original_comp (comp);
+
                        *out_components = g_slist_prepend (*out_components, comp);
                }
        }
@@ -1122,6 +1190,8 @@ ecb_ews_components_to_infos (ECalMetaBackend *meta_backend,
                if (!uid)
                        continue;
 
+               ecb_ews_store_original_comp (comp);
+
                instances = g_hash_table_lookup (sorted_by_uids, uid);
                g_hash_table_insert (sorted_by_uids, (gpointer) uid, g_slist_prepend (instances, comp));
        }
@@ -1850,7 +1920,7 @@ ecb_ews_filter_out_unchanged_instances (const GSList *to_save_instances,
 
        for (link = (GSList *) existing_instances; link; link = g_slist_next (link)) {
                ECalComponent *comp = link->data;
-               ECalComponentId *id = NULL;
+               ECalComponentId *id;
 
                id = e_cal_component_get_id (comp);
                if (id)
@@ -2404,6 +2474,24 @@ ecb_ews_save_component_sync (ECalMetaBackend *meta_backend,
 
                success = uid && e_cal_cache_get_components_by_uid (cal_cache, uid, &existing, cancellable, 
error) && existing;
 
+               if (success) {
+                       GSList *link;
+
+                       /* This is for offline changes, where the component in the cache
+                          is already modified, while the original, the one on the server,
+                          is different. Using the cached component in this case generates
+                          empty UpdateItem request and nothing is saved. */
+                       for (link = existing; link; link = g_slist_next (link)) {
+                               ECalComponent *comp = link->data;
+
+                               comp = ecb_ews_restore_original_comp (comp);
+                               if (comp) {
+                                       g_object_unref (link->data);
+                                       link->data = comp;
+                               }
+                       }
+               }
+
                if (success)
                        ecb_ews_filter_out_unchanged_instances (instances, existing, &changed_instances, 
&removed_instances);
 
diff --git a/src/server/e-ews-connection.c b/src/server/e-ews-connection.c
index 48517ca..0c15ec5 100644
--- a/src/server/e-ews-connection.c
+++ b/src/server/e-ews-connection.c
@@ -797,7 +797,8 @@ ews_response_cb (SoupSession *session,
        } else if (msg->status_code == SOUP_STATUS_CANT_RESOLVE ||
                   msg->status_code == SOUP_STATUS_CANT_RESOLVE_PROXY ||
                   msg->status_code == SOUP_STATUS_CANT_CONNECT ||
-                  msg->status_code == SOUP_STATUS_CANT_CONNECT_PROXY) {
+                  msg->status_code == SOUP_STATUS_CANT_CONNECT_PROXY ||
+                  msg->status_code == SOUP_STATUS_IO_ERROR) {
                g_simple_async_result_set_error (
                        enode->simple,
                        EWS_CONNECTION_ERROR,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]