1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6
7 #include "evolution-ews-config.h"
8
9 #include <string.h>
10 #include <time.h>
11
12 #include <glib.h>
13 #include <glib/gi18n-lib.h>
14
15 #include <libedata-book/libedata-book.h>
16
17 #include "common/camel-m365-settings.h"
18 #include "common/e-m365-connection.h"
19 #include "common/e-source-m365-folder.h"
20
21 #include "e-book-backend-m365.h"
22
23 #ifdef G_OS_WIN32
24 #ifdef gmtime_r
25 #undef gmtime_r
26 #endif
27
28 /* The gmtime() in Microsoft's C library is MT-safe */
29 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
30 #endif
31
32 #define EC_ERROR_EX(_code,_msg) e_client_error_create (_code, _msg)
33 #define EBC_ERROR_EX(_code,_msg) e_book_client_error_create (_code, _msg)
34
35 #define LOCK(_bb) g_rec_mutex_lock (&_bb->priv->property_lock)
36 #define UNLOCK(_bb) g_rec_mutex_unlock (&_bb->priv->property_lock)
37
38 struct _EBookBackendM365Private {
39 GRecMutex property_lock;
40 EM365Connection *cnc;
41 gchar *folder_id;
42 };
43
G_DEFINE_TYPE_WITH_PRIVATE(EBookBackendM365,e_book_backend_m365,E_TYPE_BOOK_META_BACKEND)44 G_DEFINE_TYPE_WITH_PRIVATE (EBookBackendM365, e_book_backend_m365, E_TYPE_BOOK_META_BACKEND)
45
46 static void
47 ebb_m365_contact_get_string_attribute (EM365Contact *m365_contact,
48 EContact *inout_contact,
49 EContactField field_id,
50 const gchar * (*m365_get_func) (EM365Contact *contact))
51 {
52 e_contact_set (inout_contact, field_id, m365_get_func (m365_contact));
53 }
54
55 static void
ebb_m365_contact_add_string_attribute(EContact * new_contact,EContact * old_contact,EContactField field_id,JsonBuilder * builder,void (* m365_add_func)(JsonBuilder * builder,const gchar * value))56 ebb_m365_contact_add_string_attribute (EContact *new_contact,
57 EContact *old_contact,
58 EContactField field_id,
59 JsonBuilder *builder,
60 void (* m365_add_func) (JsonBuilder *builder,
61 const gchar *value))
62 {
63 const gchar *new_value, *old_value;
64
65 g_return_if_fail (m365_add_func != NULL);
66
67 new_value = e_contact_get_const (new_contact, field_id);
68 old_value = old_contact ? e_contact_get_const (old_contact, field_id) : NULL;
69
70 if (g_strcmp0 (new_value, old_value) != 0)
71 m365_add_func (builder, new_value);
72 }
73
74 static gboolean
ebb_m365_contact_get_rev(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)75 ebb_m365_contact_get_rev (EBookBackendM365 *bbm365,
76 EM365Contact *m365_contact,
77 EContact *inout_contact,
78 EContactField field_id,
79 EM365Connection *cnc,
80 GCancellable *cancellable,
81 GError **error)
82 {
83 gchar time_string[100] = { 0 };
84 struct tm stm;
85 time_t value;
86
87 value = e_m365_contact_get_last_modified_date_time (m365_contact);
88
89 if (value <= (time_t) 0)
90 value = time (NULL);
91
92 gmtime_r (&value, &stm);
93 strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", &stm);
94
95 e_contact_set (inout_contact, field_id, time_string);
96
97 return TRUE;
98 }
99
100 static gboolean
ebb_m365_contact_get_birthday(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)101 ebb_m365_contact_get_birthday (EBookBackendM365 *bbm365,
102 EM365Contact *m365_contact,
103 EContact *inout_contact,
104 EContactField field_id,
105 EM365Connection *cnc,
106 GCancellable *cancellable,
107 GError **error)
108 {
109 time_t value;
110 gboolean exists = FALSE;
111
112 value = e_m365_contact_get_birthday (m365_contact, &exists);
113
114 if (exists) {
115 EContactDate dt;
116 struct tm stm;
117
118 gmtime_r (&value, &stm);
119
120 dt.year = stm.tm_year + 1900;
121 dt.month = stm.tm_mon + 1;
122 dt.day = stm.tm_mday;
123
124 e_contact_set (inout_contact, field_id, &dt);
125 }
126
127 return TRUE;
128 }
129
130 static gboolean
ebb_m365_contact_add_birthday(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)131 ebb_m365_contact_add_birthday (EBookBackendM365 *bbm365,
132 EContact *new_contact,
133 EContact *old_contact,
134 EContactField field_id,
135 const gchar *m365_id,
136 JsonBuilder *builder,
137 GCancellable *cancellable,
138 GError **error)
139 {
140 EContactDate *old_dt = NULL;
141 EContactDate *new_dt = NULL;
142
143 new_dt = e_contact_get (new_contact, field_id);
144 old_dt = old_contact ? e_contact_get (old_contact, field_id) : NULL;
145
146 if (!e_contact_date_equal (new_dt, old_dt)) {
147 if (new_dt) {
148 GDateTime *gdt;
149 time_t value = (time_t) 0;
150
151 gdt = g_date_time_new_local (new_dt->year, new_dt->month, new_dt->day, 11, 59, 0.0);
152
153 if (gdt) {
154 value = g_date_time_to_unix (gdt);
155 value = value - (value % (24 * 60 * 60));
156 value = value + (((12 * 60) - 1) * 60);
157
158 g_date_time_unref (gdt);
159 }
160
161 e_m365_contact_add_birthday (builder, value);
162 } else {
163 e_m365_contact_add_birthday (builder, (time_t) 0);
164 }
165 }
166
167 e_contact_date_free (new_dt);
168 e_contact_date_free (old_dt);
169
170 return TRUE;
171 }
172
173 static gboolean
ebb_m365_contact_get_address(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)174 ebb_m365_contact_get_address (EBookBackendM365 *bbm365,
175 EM365Contact *m365_contact,
176 EContact *inout_contact,
177 EContactField field_id,
178 EM365Connection *cnc,
179 GCancellable *cancellable,
180 GError **error)
181 {
182 EM365PhysicalAddress *phys_address = NULL;
183
184 if (field_id == E_CONTACT_ADDRESS_WORK)
185 phys_address = e_m365_contact_get_business_address (m365_contact);
186 else if (field_id == E_CONTACT_ADDRESS_HOME)
187 phys_address = e_m365_contact_get_home_address (m365_contact);
188 else if (field_id == E_CONTACT_ADDRESS_OTHER)
189 phys_address = e_m365_contact_get_other_address (m365_contact);
190 else
191 g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
192
193 if (phys_address) {
194 EContactAddress addr;
195
196 memset (&addr, 0, sizeof (EContactAddress));
197
198 addr.locality = (gchar *) e_m365_physical_address_get_city (phys_address);
199 addr.country = (gchar *) e_m365_physical_address_get_country_or_region (phys_address);
200 addr.code = (gchar *) e_m365_physical_address_get_postal_code (phys_address);
201 addr.region = (gchar *) e_m365_physical_address_get_state (phys_address);
202 addr.street = (gchar *) e_m365_physical_address_get_street (phys_address);
203
204 if (addr.locality || addr.country || addr.code || addr.region || addr.street)
205 e_contact_set (inout_contact, field_id, &addr);
206 else
207 e_contact_set (inout_contact, field_id, NULL);
208 }
209
210 return TRUE;
211 }
212
213 static gboolean
ebb_m365_contact_address_equal(const EContactAddress * addr1,const EContactAddress * addr2)214 ebb_m365_contact_address_equal (const EContactAddress *addr1,
215 const EContactAddress *addr2)
216 {
217 if (!addr1 && !addr2)
218 return TRUE;
219
220 if ((addr1 && !addr2) || (!addr1 && addr2))
221 return FALSE;
222
223 return /* g_strcmp0 (addr1->address_format, addr2->address_format) == 0 && */
224 g_strcmp0 (addr1->po, addr2->po) == 0 &&
225 g_strcmp0 (addr1->ext, addr2->ext) == 0 &&
226 g_strcmp0 (addr1->street, addr2->street) == 0 &&
227 g_strcmp0 (addr1->locality, addr2->locality) == 0 &&
228 g_strcmp0 (addr1->region, addr2->region) == 0 &&
229 g_strcmp0 (addr1->code, addr2->code) == 0 &&
230 g_strcmp0 (addr1->country, addr2->country) == 0;
231 }
232
233 static gboolean
ebb_m365_contact_add_address(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)234 ebb_m365_contact_add_address (EBookBackendM365 *bbm365,
235 EContact *new_contact,
236 EContact *old_contact,
237 EContactField field_id,
238 const gchar *m365_id,
239 JsonBuilder *builder,
240 GCancellable *cancellable,
241 GError **error)
242 {
243 EContactAddress *new_addr, *old_addr;
244
245 new_addr = e_contact_get (new_contact, field_id);
246 old_addr = old_contact ? e_contact_get (old_contact, field_id) : NULL;
247
248 if (!ebb_m365_contact_address_equal (new_addr, old_addr)) {
249 void (* add_func) (JsonBuilder *builder,
250 const gchar *city,
251 const gchar *country_or_region,
252 const gchar *postal_code,
253 const gchar *state,
254 const gchar *street) = NULL;
255
256 if (field_id == E_CONTACT_ADDRESS_WORK)
257 add_func = e_m365_contact_add_business_address;
258 else if (field_id == E_CONTACT_ADDRESS_HOME)
259 add_func = e_m365_contact_add_home_address;
260 else if (field_id == E_CONTACT_ADDRESS_OTHER)
261 add_func = e_m365_contact_add_other_address;
262 else
263 g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
264
265 if (add_func) {
266 if (new_addr) {
267 add_func (builder, new_addr->locality, new_addr->country, new_addr->code, new_addr->region, new_addr->street);
268 } else {
269 add_func (builder, NULL, NULL, NULL, NULL, NULL);
270 }
271 }
272 }
273
274 e_contact_address_free (new_addr);
275 e_contact_address_free (old_addr);
276
277 return TRUE;
278 }
279
280 static gboolean
ebb_m365_string_values_equal(GSList * new_values,GSList * old_values)281 ebb_m365_string_values_equal (GSList *new_values, /* const gchar * */
282 GSList *old_values) /* const gchar * */
283 {
284 GHashTable *values;
285 GSList *link;
286 gboolean equal = TRUE;
287
288 if (g_slist_length (new_values) != g_slist_length (old_values))
289 return FALSE;
290
291 values = g_hash_table_new (g_str_hash, g_str_equal);
292
293 for (link = new_values; link; link = g_slist_next (link)) {
294 gchar *value = link->data;
295
296 if (value)
297 g_hash_table_add (values, value);
298 }
299
300 for (link = old_values; link && equal; link = g_slist_next (link)) {
301 const gchar *value = link->data;
302
303 if (value)
304 equal = g_hash_table_remove (values, value);
305 }
306
307 equal = equal && !g_hash_table_size (values);
308
309 g_hash_table_destroy (values);
310
311 return equal;
312 }
313
314 static gboolean
ebb_m365_string_list_values_equal(GList * new_values,GList * old_values)315 ebb_m365_string_list_values_equal (GList *new_values, /* const gchar * */
316 GList *old_values) /* const gchar * */
317 {
318 GHashTable *values;
319 GList *link;
320 gboolean equal = TRUE;
321
322 if (g_list_length (new_values) != g_list_length (old_values))
323 return FALSE;
324
325 values = g_hash_table_new (g_str_hash, g_str_equal);
326
327 for (link = new_values; link; link = g_list_next (link)) {
328 gchar *value = link->data;
329
330 if (value)
331 g_hash_table_add (values, value);
332 }
333
334 for (link = old_values; link && equal; link = g_list_next (link)) {
335 const gchar *value = link->data;
336
337 if (value)
338 equal = g_hash_table_remove (values, value);
339 }
340
341 equal = equal && !g_hash_table_size (values);
342
343 g_hash_table_destroy (values);
344
345 return equal;
346 }
347
348 static gboolean
ebb_m365_contact_get_phone(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)349 ebb_m365_contact_get_phone (EBookBackendM365 *bbm365,
350 EM365Contact *m365_contact,
351 EContact *inout_contact,
352 EContactField field_id,
353 EM365Connection *cnc,
354 GCancellable *cancellable,
355 GError **error)
356 {
357 JsonArray *values = NULL;
358 const gchar *type_val = NULL;
359
360 if (field_id == E_CONTACT_PHONE_BUSINESS) {
361 values = e_m365_contact_get_business_phones (m365_contact);
362 type_val = "WORK";
363 } else if (field_id == E_CONTACT_PHONE_HOME) {
364 values = e_m365_contact_get_home_phones (m365_contact);
365 type_val = "HOME";
366 } else {
367 g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
368 }
369
370 if (values) {
371 EVCard *vcard = E_VCARD (inout_contact);
372 guint ii, len;
373
374 len = json_array_get_length (values);
375
376 for (ii = 0; ii < len; ii++) {
377 const gchar *str = json_array_get_string_element (values, len - ii - 1);
378
379 if (str && *str) {
380 EVCardAttributeParam *param;
381 EVCardAttribute *attr;
382
383 attr = e_vcard_attribute_new (NULL, EVC_TEL);
384 param = e_vcard_attribute_param_new (EVC_TYPE);
385
386 e_vcard_attribute_add_param_with_value (attr, param, type_val);
387 e_vcard_add_attribute_with_value (vcard, attr, str);
388 }
389 }
390 }
391
392 return TRUE;
393 }
394
395 static GSList * /* gchar * */
ebb_m365_extract_phones(EContact * contact,const gchar * only_type)396 ebb_m365_extract_phones (EContact *contact,
397 const gchar *only_type) /* NULL for anything but known types */
398 {
399 GSList *phones = NULL;
400 GList *attrs, *link;
401
402 if (!contact)
403 return NULL;
404
405 attrs = e_vcard_get_attributes (E_VCARD (contact));
406
407 for (link = attrs; link; link = g_list_next (link)) {
408 EVCardAttribute *attr = link->data;
409 gboolean use_it = FALSE;
410
411 if (!attr || !e_vcard_attribute_get_name (attr) ||
412 g_ascii_strcasecmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
413 continue;
414
415 if (only_type) {
416 use_it = e_vcard_attribute_has_type (attr, only_type);
417 } else {
418 use_it = !e_vcard_attribute_has_type (attr, "WORK") &&
419 !e_vcard_attribute_has_type (attr, "CELL");
420 }
421
422 if (use_it)
423 phones = g_slist_prepend (phones, e_vcard_attribute_get_value (attr));
424 }
425
426 return g_slist_reverse (phones);
427 }
428
429 static gboolean
ebb_m365_contact_add_phone(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)430 ebb_m365_contact_add_phone (EBookBackendM365 *bbm365,
431 EContact *new_contact,
432 EContact *old_contact,
433 EContactField field_id,
434 const gchar *m365_id,
435 JsonBuilder *builder,
436 GCancellable *cancellable,
437 GError **error)
438 {
439 void (* begin_func) (JsonBuilder *builder) = NULL;
440 void (* end_func) (JsonBuilder *builder) = NULL;
441 void (* add_func) (JsonBuilder *builder, const gchar *value) = NULL;
442 const gchar *type_val = NULL;
443 GSList *new_values, *old_values;
444
445 if (field_id == E_CONTACT_PHONE_BUSINESS) {
446 begin_func = e_m365_contact_begin_business_phones;
447 end_func = e_m365_contact_end_business_phones;
448 add_func = e_m365_contact_add_business_phone;
449 type_val = "WORK";
450 } else if (field_id == E_CONTACT_PHONE_HOME) {
451 begin_func = e_m365_contact_begin_home_phones;
452 end_func = e_m365_contact_end_home_phones;
453 add_func = e_m365_contact_add_home_phone;
454 type_val = NULL; /* everything else is treated as "HOME" phone */
455 } else {
456 g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
457 }
458
459 new_values = ebb_m365_extract_phones (new_contact, type_val);
460 old_values = ebb_m365_extract_phones (old_contact, type_val);
461
462 if (!ebb_m365_string_values_equal (new_values, old_values)) {
463 GSList *link;
464
465 begin_func (builder);
466
467 for (link = new_values; link; link = g_slist_next (link)) {
468 const gchar *value = link->data;
469
470 add_func (builder, value);
471 }
472
473 end_func (builder);
474 }
475
476 g_slist_free_full (new_values, g_free);
477 g_slist_free_full (old_values, g_free);
478
479 return TRUE;
480 }
481
482 static gboolean
ebb_m365_contact_get_categories(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)483 ebb_m365_contact_get_categories (EBookBackendM365 *bbm365,
484 EM365Contact *m365_contact,
485 EContact *inout_contact,
486 EContactField field_id,
487 EM365Connection *cnc,
488 GCancellable *cancellable,
489 GError **error)
490 {
491 JsonArray *values;
492
493 values = e_m365_contact_get_categories (m365_contact);
494
495 if (values) {
496 GString *categories_str = NULL;
497 guint ii, len;
498
499 len = json_array_get_length (values);
500
501 for (ii = 0; ii < len; ii++) {
502 const gchar *str = json_array_get_string_element (values, ii);
503
504 if (str && *str) {
505 if (!categories_str) {
506 categories_str = g_string_new (str);
507 } else {
508 g_string_append_c (categories_str, ',');
509 g_string_append (categories_str, str);
510 }
511 }
512 }
513
514 if (categories_str) {
515 e_contact_set (inout_contact, field_id, categories_str->str);
516 g_string_free (categories_str, TRUE);
517 }
518 }
519
520 return TRUE;
521 }
522
523 static GSList *
ebb_m365_extract_categories(EContact * contact,EContactField field_id)524 ebb_m365_extract_categories (EContact *contact,
525 EContactField field_id)
526 {
527 GSList *categories = NULL;
528 const gchar *str;
529
530 if (!contact)
531 return NULL;
532
533 str = e_contact_get_const (contact, field_id);
534
535 if (str && *str) {
536 gchar **split_str;
537 gint ii;
538
539 split_str = g_strsplit (str, ",", -1);
540
541 for (ii = 0; split_str && split_str[ii]; ii++) {
542 gchar *item = split_str[ii];
543
544 if (item && *item)
545 categories = g_slist_prepend (categories, item);
546 else
547 g_free (item);
548
549 split_str[ii] = NULL;
550 }
551
552 g_free (split_str);
553 }
554
555 return g_slist_reverse (categories);
556 }
557
558 static gboolean
ebb_m365_contact_add_categories(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)559 ebb_m365_contact_add_categories (EBookBackendM365 *bbm365,
560 EContact *new_contact,
561 EContact *old_contact,
562 EContactField field_id,
563 const gchar *m365_id,
564 JsonBuilder *builder,
565 GCancellable *cancellable,
566 GError **error)
567 {
568 GSList *new_values, *old_values;
569
570 new_values = ebb_m365_extract_categories (new_contact, field_id);
571 old_values = ebb_m365_extract_categories (old_contact, field_id);
572
573 if (!ebb_m365_string_values_equal (new_values, old_values)) {
574 GSList *link;
575
576 e_m365_contact_begin_categories (builder);
577
578 for (link = new_values; link; link = g_slist_next (link)) {
579 const gchar *value = link->data;
580
581 e_m365_contact_add_category (builder, value);
582 }
583
584 e_m365_contact_end_categories (builder);
585 }
586
587 g_slist_free_full (new_values, g_free);
588 g_slist_free_full (old_values, g_free);
589
590 return TRUE;
591 }
592
593 static gboolean
ebb_m365_contact_get_emails(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)594 ebb_m365_contact_get_emails (EBookBackendM365 *bbm365,
595 EM365Contact *m365_contact,
596 EContact *inout_contact,
597 EContactField field_id,
598 EM365Connection *cnc,
599 GCancellable *cancellable,
600 GError **error)
601 {
602 JsonArray *values;
603
604 values = e_m365_contact_get_email_addresses (m365_contact);
605
606 if (values) {
607 EVCard *vcard = E_VCARD (inout_contact);
608 guint ii, len;
609
610 len = json_array_get_length (values);
611
612 for (ii = 0; ii < len; ii++) {
613 EM365EmailAddress *address = json_array_get_object_element (values, len - ii - 1);
614
615 if (address) {
616 EVCardAttribute *attr;
617
618 attr = e_vcard_attribute_new (NULL, EVC_EMAIL);
619 e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE), "OTHER");
620
621 if (g_strcmp0 (e_m365_email_address_get_name (address), e_m365_email_address_get_address (address)) == 0) {
622 e_vcard_add_attribute_with_value (vcard, attr, e_m365_email_address_get_address (address));
623 } else {
624 gchar *formatted;
625
626 formatted = camel_internet_address_format_address (
627 e_m365_email_address_get_name (address),
628 e_m365_email_address_get_address (address));
629
630 if (formatted && *formatted)
631 e_vcard_add_attribute_with_value (vcard, attr, formatted);
632 else
633 e_vcard_attribute_free (attr);
634
635 g_free (formatted);
636 }
637 }
638 }
639 }
640
641 return TRUE;
642 }
643
644 static gboolean
ebb_m365_parse_qp_email(const gchar * string,gchar ** name,gchar ** email)645 ebb_m365_parse_qp_email (const gchar *string,
646 gchar **name,
647 gchar **email)
648 {
649 struct _camel_header_address *address;
650 gboolean res = FALSE;
651
652 address = camel_header_address_decode (string, "UTF-8");
653
654 if (address) {
655 /* report success only when we have filled both name and email address */
656 if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
657 *name = g_strdup (address->name);
658 *email = g_strdup (address->v.addr);
659 res = TRUE;
660 }
661
662 camel_header_address_unref (address);
663 }
664
665 if (!res) {
666 CamelInternetAddress *addr = camel_internet_address_new ();
667 const gchar *const_name = NULL, *const_email = NULL;
668
669 if (camel_address_unformat (CAMEL_ADDRESS (addr), string) == 1 &&
670 camel_internet_address_get (addr, 0, &const_name, &const_email) &&
671 const_name && *const_name && const_email && *const_email) {
672 *name = g_strdup (const_name);
673 *email = g_strdup (const_email);
674 res = TRUE;
675 }
676
677 g_clear_object (&addr);
678 }
679
680 return res;
681 }
682
683 static gboolean
ebb_m365_contact_add_emails(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)684 ebb_m365_contact_add_emails (EBookBackendM365 *bbm365,
685 EContact *new_contact,
686 EContact *old_contact,
687 EContactField field_id,
688 const gchar *m365_id,
689 JsonBuilder *builder,
690 GCancellable *cancellable,
691 GError **error)
692 {
693 GList *new_values, *old_values;
694
695 new_values = e_contact_get (new_contact, field_id);
696 old_values = old_contact ? e_contact_get (old_contact, field_id) : NULL;
697
698 if (!ebb_m365_string_list_values_equal (new_values, old_values)) {
699 GList *link;
700
701 e_m365_contact_begin_email_addresses (builder);
702
703 for (link = new_values; link; link = g_list_next (link)) {
704 const gchar *value = link->data;
705 gchar *name = NULL, *address = NULL;
706
707 if (ebb_m365_parse_qp_email (value, &name, &address))
708 e_m365_add_email_address (builder, NULL, name, address);
709 else
710 e_m365_add_email_address (builder, NULL, NULL, value);
711
712 g_free (name);
713 g_free (address);
714 }
715
716 e_m365_contact_end_email_addresses (builder);
717 }
718
719 g_list_free_full (new_values, g_free);
720 g_list_free_full (old_values, g_free);
721
722 return TRUE;
723 }
724
725 static gboolean
ebb_m365_contact_add_file_as(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)726 ebb_m365_contact_add_file_as (EBookBackendM365 *bbm365,
727 EContact *new_contact,
728 EContact *old_contact,
729 EContactField field_id,
730 const gchar *m365_id,
731 JsonBuilder *builder,
732 GCancellable *cancellable,
733 GError **error)
734 {
735 const gchar *new_value;
736
737 ebb_m365_contact_add_string_attribute (new_contact, old_contact, field_id, builder, e_m365_contact_add_file_as);
738
739 new_value = e_contact_get_const (new_contact, E_CONTACT_FILE_AS);
740
741 /* Set it always, to not be overwritten by server re-calculations on other property changes */
742 e_m365_contact_add_display_name (builder, new_value);
743
744 return TRUE;
745 }
746
747 static gboolean
ebb_m365_contact_get_generation(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)748 ebb_m365_contact_get_generation (EBookBackendM365 *bbm365,
749 EM365Contact *m365_contact,
750 EContact *inout_contact,
751 EContactField field_id,
752 EM365Connection *cnc,
753 GCancellable *cancellable,
754 GError **error)
755 {
756 const gchar *value;
757
758 value = e_m365_contact_get_generation (m365_contact);
759
760 if (value && *value) {
761 EContactName *name = e_contact_get (inout_contact, field_id);
762 gchar *prev;
763
764 if (!name)
765 name = e_contact_name_new ();
766
767 prev = name->suffixes;
768 name->suffixes = (gchar *) value;
769
770 e_contact_set (inout_contact, field_id, name);
771
772 name->suffixes = prev;
773 e_contact_name_free (name);
774 }
775
776 return TRUE;
777 }
778
779 static gboolean
ebb_m365_contact_add_generation(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)780 ebb_m365_contact_add_generation (EBookBackendM365 *bbm365,
781 EContact *new_contact,
782 EContact *old_contact,
783 EContactField field_id,
784 const gchar *m365_id,
785 JsonBuilder *builder,
786 GCancellable *cancellable,
787 GError **error)
788 {
789 EContactName *new_value, *old_value;
790
791 new_value = e_contact_get (new_contact, field_id);
792 old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
793
794 if (!(new_value && old_value && g_strcmp0 (new_value->suffixes, old_value->suffixes) == 0))
795 e_m365_contact_add_generation (builder, new_value ? new_value->suffixes : NULL);
796
797 e_contact_name_free (new_value);
798 e_contact_name_free (old_value);
799
800 return TRUE;
801 }
802
803 static gboolean
ebb_m365_contact_get_im_addresses(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)804 ebb_m365_contact_get_im_addresses (EBookBackendM365 *bbm365,
805 EM365Contact *m365_contact,
806 EContact *inout_contact,
807 EContactField field_id,
808 EM365Connection *cnc,
809 GCancellable *cancellable,
810 GError **error)
811 {
812 JsonArray *values;
813
814 values = e_m365_contact_get_im_addresses (m365_contact);
815
816 if (values) {
817 EVCard *vcard = E_VCARD (inout_contact);
818 const gchar *field_name = e_contact_vcard_attribute (field_id);
819 guint ii, len;
820
821 len = json_array_get_length (values);
822
823 for (ii = 0; ii < len; ii++) {
824 const gchar *str = json_array_get_string_element (values, len - ii - 1);
825
826 if (str && *str) {
827 EVCardAttribute *attr;
828
829 attr = e_vcard_attribute_new (NULL, field_name);
830
831 e_vcard_add_attribute_with_value (vcard, attr, str);
832 }
833 }
834 }
835
836 return TRUE;
837 }
838
839 static GSList * /* gchar * */
ebb_m365_extract_im_addresses(EContact * contact)840 ebb_m365_extract_im_addresses (EContact *contact)
841 {
842 GSList *ims = NULL;
843 GList *attrs, *link;
844
845 if (!contact)
846 return NULL;
847
848 attrs = e_vcard_get_attributes (E_VCARD (contact));
849
850 for (link = attrs; link; link = g_list_next (link)) {
851 EVCardAttribute *attr = link->data;
852 const gchar *name;
853
854 if (!attr)
855 continue;
856
857 name = e_vcard_attribute_get_name (attr);
858
859 if (!name || (
860 g_ascii_strcasecmp (name, EVC_X_GOOGLE_TALK) != 0 &&
861 g_ascii_strcasecmp (name, EVC_X_SKYPE) != 0 &&
862 g_ascii_strcasecmp (name, EVC_X_GADUGADU) != 0 &&
863 g_ascii_strcasecmp (name, EVC_X_AIM) != 0 &&
864 g_ascii_strcasecmp (name, EVC_X_GROUPWISE) != 0 &&
865 g_ascii_strcasecmp (name, EVC_X_JABBER) != 0 &&
866 g_ascii_strcasecmp (name, EVC_X_YAHOO) != 0 &&
867 g_ascii_strcasecmp (name, EVC_X_MSN) != 0 &&
868 g_ascii_strcasecmp (name, EVC_X_ICQ) != 0))
869 continue;
870
871 ims = g_slist_prepend (ims, e_vcard_attribute_get_value (attr));
872 }
873
874 return g_slist_reverse (ims);
875 }
876
877 static gboolean
ebb_m365_contact_add_im_addresses(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)878 ebb_m365_contact_add_im_addresses (EBookBackendM365 *bbm365,
879 EContact *new_contact,
880 EContact *old_contact,
881 EContactField field_id,
882 const gchar *m365_id,
883 JsonBuilder *builder,
884 GCancellable *cancellable,
885 GError **error)
886 {
887 GSList *new_values, *old_values;
888
889 new_values = ebb_m365_extract_im_addresses (new_contact);
890 old_values = ebb_m365_extract_im_addresses (old_contact);
891
892 if (!ebb_m365_string_values_equal (new_values, old_values)) {
893 GSList *link;
894
895 e_m365_contact_begin_im_addresses (builder);
896
897 for (link = new_values; link; link = g_slist_next (link)) {
898 const gchar *value = link->data;
899
900 if (value && *value)
901 e_m365_contact_add_im_address (builder, value);
902 }
903
904 e_m365_contact_end_im_addresses (builder);
905 }
906
907 g_slist_free_full (new_values, g_free);
908 g_slist_free_full (old_values, g_free);
909
910 return TRUE;
911 }
912
913 static gboolean
ebb_m365_contact_get_middle_name(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)914 ebb_m365_contact_get_middle_name (EBookBackendM365 *bbm365,
915 EM365Contact *m365_contact,
916 EContact *inout_contact,
917 EContactField field_id,
918 EM365Connection *cnc,
919 GCancellable *cancellable,
920 GError **error)
921 {
922 const gchar *value;
923
924 value = e_m365_contact_get_middle_name (m365_contact);
925
926 if (value && *value) {
927 EContactName *name = e_contact_get (inout_contact, field_id);
928 gchar *prev;
929
930 if (!name)
931 name = e_contact_name_new ();
932
933 prev = name->additional;
934 name->additional = (gchar *) value;
935
936 e_contact_set (inout_contact, field_id, name);
937
938 name->additional = prev;
939 e_contact_name_free (name);
940 }
941
942 return TRUE;
943 }
944
945 static gboolean
ebb_m365_contact_add_middle_name(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)946 ebb_m365_contact_add_middle_name (EBookBackendM365 *bbm365,
947 EContact *new_contact,
948 EContact *old_contact,
949 EContactField field_id,
950 const gchar *m365_id,
951 JsonBuilder *builder,
952 GCancellable *cancellable,
953 GError **error)
954 {
955 EContactName *new_value, *old_value;
956
957 new_value = e_contact_get (new_contact, field_id);
958 old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
959
960 if (!(new_value && old_value && g_strcmp0 (new_value->additional, old_value->additional) == 0))
961 e_m365_contact_add_middle_name (builder, new_value ? new_value->additional : NULL);
962
963 e_contact_name_free (new_value);
964 e_contact_name_free (old_value);
965
966 return TRUE;
967 }
968
969 static gboolean
ebb_m365_contact_get_title(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)970 ebb_m365_contact_get_title (EBookBackendM365 *bbm365,
971 EM365Contact *m365_contact,
972 EContact *inout_contact,
973 EContactField field_id,
974 EM365Connection *cnc,
975 GCancellable *cancellable,
976 GError **error)
977 {
978 const gchar *value;
979
980 value = e_m365_contact_get_title (m365_contact);
981
982 if (value && *value) {
983 EContactName *name = e_contact_get (inout_contact, field_id);
984 gchar *prev;
985
986 if (!name)
987 name = e_contact_name_new ();
988
989 prev = name->prefixes;
990 name->prefixes = (gchar *) value;
991
992 e_contact_set (inout_contact, field_id, name);
993
994 name->prefixes = prev;
995 e_contact_name_free (name);
996 }
997
998 return TRUE;
999 }
1000
1001 static gboolean
ebb_m365_contact_add_title(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)1002 ebb_m365_contact_add_title (EBookBackendM365 *bbm365,
1003 EContact *new_contact,
1004 EContact *old_contact,
1005 EContactField field_id,
1006 const gchar *m365_id,
1007 JsonBuilder *builder,
1008 GCancellable *cancellable,
1009 GError **error)
1010 {
1011 EContactName *new_value, *old_value;
1012
1013 new_value = e_contact_get (new_contact, field_id);
1014 old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
1015
1016 if (!(new_value && old_value && g_strcmp0 (new_value->prefixes, old_value->prefixes) == 0))
1017 e_m365_contact_add_title (builder, new_value ? new_value->prefixes : NULL);
1018
1019 e_contact_name_free (new_value);
1020 e_contact_name_free (old_value);
1021
1022 return TRUE;
1023 }
1024
1025 static gboolean
ebb_m365_contact_get_photo(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EContact * inout_contact,EContactField field_id,EM365Connection * cnc,GCancellable * cancellable,GError ** error)1026 ebb_m365_contact_get_photo (EBookBackendM365 *bbm365,
1027 EM365Contact *m365_contact,
1028 EContact *inout_contact,
1029 EContactField field_id,
1030 EM365Connection *cnc,
1031 GCancellable *cancellable,
1032 GError **error)
1033 {
1034 GByteArray *photo_data = NULL;
1035 GError *local_error = NULL;
1036
1037 LOCK (bbm365);
1038
1039 if (e_m365_connection_get_contact_photo_sync (cnc, NULL, bbm365->priv->folder_id,
1040 e_m365_contact_get_id (m365_contact), &photo_data, cancellable, &local_error) &&
1041 photo_data && photo_data->len) {
1042 EContactPhoto *photo;
1043
1044 photo = e_contact_photo_new ();
1045 e_contact_photo_set_inlined (photo, photo_data->data, photo_data->len);
1046 e_contact_photo_set_mime_type (photo, "image/jpeg");
1047 e_contact_set (inout_contact, field_id, photo);
1048 e_contact_photo_free (photo);
1049 }
1050
1051 UNLOCK (bbm365);
1052
1053 if (photo_data)
1054 g_byte_array_unref (photo_data);
1055 g_clear_error (&local_error);
1056
1057 /* Even it could fail, ignore it and read as many contacts as possible, rather than stop on the first error */
1058 return TRUE;
1059 }
1060
1061 static gboolean
ebb_m365_contact_photo_equal(EContactPhoto * photo1,EContactPhoto * photo2)1062 ebb_m365_contact_photo_equal (EContactPhoto *photo1,
1063 EContactPhoto *photo2)
1064 {
1065 const guchar *data1, *data2;
1066 gsize len1 = 0, len2 = 0;
1067
1068 if (!photo1 && !photo2)
1069 return TRUE;
1070
1071 if ((photo1 && !photo2) || (!photo1 && photo2))
1072 return FALSE;
1073
1074 data1 = e_contact_photo_get_inlined (photo1, &len1);
1075 data2 = e_contact_photo_get_inlined (photo2, &len2);
1076
1077 if (!data1 && !data2)
1078 return TRUE;
1079
1080 return len1 == len2 &&
1081 memcmp (data1, data2, len1) == 0;
1082 }
1083
1084 static gboolean
ebb_m365_contact_add_photo(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,EContactField field_id,const gchar * m365_id,JsonBuilder * builder,GCancellable * cancellable,GError ** error)1085 ebb_m365_contact_add_photo (EBookBackendM365 *bbm365,
1086 EContact *new_contact,
1087 EContact *old_contact,
1088 EContactField field_id,
1089 const gchar *m365_id,
1090 JsonBuilder *builder,
1091 GCancellable *cancellable,
1092 GError **error)
1093 {
1094 EContactPhoto *new_value, *old_value;
1095
1096 new_value = e_contact_get (new_contact, field_id);
1097 old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
1098
1099 if (!ebb_m365_contact_photo_equal (new_value, old_value)) {
1100 GByteArray *jpeg_photo = NULL, tmp;
1101 GError *local_error = NULL;
1102
1103 if (new_value) {
1104 gsize len = 0;
1105
1106 tmp.data = (guchar *) e_contact_photo_get_inlined (new_value, &len);
1107
1108 if (len && tmp.data) {
1109 tmp.len = len;
1110 jpeg_photo = &tmp;
1111 }
1112 }
1113
1114 LOCK (bbm365);
1115
1116 if (!e_m365_connection_update_contact_photo_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1117 m365_id ? m365_id : e_contact_get_const (new_contact, E_CONTACT_UID), jpeg_photo, cancellable, &local_error)) {
1118 if (local_error) {
1119 g_propagate_error (error, local_error);
1120 local_error = NULL;
1121 }
1122 }
1123
1124 UNLOCK (bbm365);
1125
1126 g_clear_error (&local_error);
1127 }
1128
1129 e_contact_photo_free (new_value);
1130 e_contact_photo_free (old_value);
1131
1132 return TRUE;
1133 }
1134
1135 #define STRING_FIELD(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, addfn, NULL }
1136 #define COMPLEX_FIELD(fldid, getfn, addfn) { fldid, FALSE, NULL, getfn, NULL, addfn }
1137 #define COMPLEX_FIELD_2(fldid, getfn, addfn) { fldid, TRUE, NULL, getfn, NULL, addfn }
1138 #define COMPLEX_ADDFN(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, NULL, addfn }
1139
1140 struct _mappings {
1141 EContactField field_id;
1142 gboolean add_in_second_go;
1143 const gchar * (* m365_get_func) (EM365Contact *m365_contact);
1144 gboolean (* get_func) (EBookBackendM365 *bbm365,
1145 EM365Contact *m365_contact,
1146 EContact *inout_contact,
1147 EContactField field_id,
1148 EM365Connection *cnc,
1149 GCancellable *cancellable,
1150 GError **error);
1151 void (* m365_add_func) (JsonBuilder *builder,
1152 const gchar *value);
1153 gboolean (* add_func) (EBookBackendM365 *bbm365,
1154 EContact *new_contact,
1155 EContact *old_contact, /* nullable */
1156 EContactField field_id,
1157 const gchar *m365_id,
1158 JsonBuilder *builder,
1159 GCancellable *cancellable,
1160 GError **error);
1161 } mappings[] = {
1162 STRING_FIELD (E_CONTACT_UID, e_m365_contact_get_id, NULL),
1163 COMPLEX_FIELD (E_CONTACT_REV, ebb_m365_contact_get_rev, NULL),
1164 STRING_FIELD (E_CONTACT_ASSISTANT, e_m365_contact_get_assistant_name, e_m365_contact_add_assistant_name),
1165 COMPLEX_FIELD (E_CONTACT_BIRTH_DATE, ebb_m365_contact_get_birthday, ebb_m365_contact_add_birthday),
1166 COMPLEX_FIELD (E_CONTACT_ADDRESS_WORK, ebb_m365_contact_get_address, ebb_m365_contact_add_address),
1167 STRING_FIELD (E_CONTACT_HOMEPAGE_URL, e_m365_contact_get_business_home_page, e_m365_contact_add_business_home_page),
1168 COMPLEX_FIELD (E_CONTACT_PHONE_BUSINESS, ebb_m365_contact_get_phone, ebb_m365_contact_add_phone),
1169 COMPLEX_FIELD (E_CONTACT_CATEGORIES, ebb_m365_contact_get_categories, ebb_m365_contact_add_categories),
1170 STRING_FIELD (E_CONTACT_ORG, e_m365_contact_get_company_name, e_m365_contact_add_company_name),
1171 STRING_FIELD (E_CONTACT_ORG_UNIT, e_m365_contact_get_department, e_m365_contact_add_department),
1172 COMPLEX_FIELD (E_CONTACT_EMAIL, ebb_m365_contact_get_emails, ebb_m365_contact_add_emails),
1173 COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_generation, ebb_m365_contact_add_generation),
1174 STRING_FIELD (E_CONTACT_GIVEN_NAME, e_m365_contact_get_given_name, e_m365_contact_add_given_name),
1175 COMPLEX_FIELD (E_CONTACT_ADDRESS_HOME, ebb_m365_contact_get_address, ebb_m365_contact_add_address),
1176 COMPLEX_FIELD (E_CONTACT_PHONE_HOME, ebb_m365_contact_get_phone, ebb_m365_contact_add_phone),
1177 COMPLEX_FIELD (E_CONTACT_IM_MSN, ebb_m365_contact_get_im_addresses, ebb_m365_contact_add_im_addresses),
1178 /* STRING_FIELD (???, e_m365_contact_get_initials, e_m365_contact_add_initials), */
1179 STRING_FIELD (E_CONTACT_TITLE, e_m365_contact_get_job_title, e_m365_contact_add_job_title),
1180 STRING_FIELD (E_CONTACT_MANAGER, e_m365_contact_get_manager, e_m365_contact_add_manager),
1181 COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_middle_name, ebb_m365_contact_add_middle_name),
1182 STRING_FIELD (E_CONTACT_PHONE_MOBILE, e_m365_contact_get_mobile_phone, e_m365_contact_add_mobile_phone),
1183 STRING_FIELD (E_CONTACT_NICKNAME, e_m365_contact_get_nick_name, e_m365_contact_add_nick_name),
1184 STRING_FIELD (E_CONTACT_OFFICE, e_m365_contact_get_office_location, e_m365_contact_add_office_location),
1185 COMPLEX_FIELD (E_CONTACT_ADDRESS_OTHER, ebb_m365_contact_get_address, ebb_m365_contact_add_address),
1186 STRING_FIELD (E_CONTACT_NOTE, e_m365_contact_get_personal_notes, e_m365_contact_add_personal_notes),
1187 STRING_FIELD (E_CONTACT_ROLE, e_m365_contact_get_profession, e_m365_contact_add_profession),
1188 STRING_FIELD (E_CONTACT_SPOUSE, e_m365_contact_get_spouse_name, e_m365_contact_add_spouse_name),
1189 STRING_FIELD (E_CONTACT_FAMILY_NAME, e_m365_contact_get_surname, e_m365_contact_add_surname),
1190 COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_title, ebb_m365_contact_add_title),
1191 /* STRING_FIELD (???, e_m365_contact_get_yomi_company_name, e_m365_contact_add_yomi_company_name), */
1192 /* STRING_FIELD (???, e_m365_contact_get_yomi_given_name, e_m365_contact_add_yomi_given_name), */
1193 /* STRING_FIELD (???, e_m365_contact_get_yomi_surname, e_m365_contact_add_yomi_surname), */
1194 COMPLEX_ADDFN (E_CONTACT_FILE_AS, e_m365_contact_get_file_as, ebb_m365_contact_add_file_as),
1195 COMPLEX_FIELD_2 (E_CONTACT_PHOTO, ebb_m365_contact_get_photo, ebb_m365_contact_add_photo)
1196 };
1197
1198 static EContact *
ebb_m365_json_contact_to_vcard(EBookBackendM365 * bbm365,EM365Contact * m365_contact,EM365Connection * cnc,gchar ** out_object,GCancellable * cancellable,GError ** error)1199 ebb_m365_json_contact_to_vcard (EBookBackendM365 *bbm365,
1200 EM365Contact *m365_contact,
1201 EM365Connection *cnc,
1202 gchar **out_object,
1203 GCancellable *cancellable,
1204 GError **error)
1205 {
1206 EContact *contact;
1207 gint ii;
1208 gboolean success = TRUE;
1209
1210 g_return_val_if_fail (m365_contact != NULL, NULL);
1211 g_return_val_if_fail (out_object != NULL, NULL);
1212
1213 *out_object = NULL;
1214
1215 contact = e_contact_new ();
1216
1217 for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1218 if (mappings[ii].m365_get_func) {
1219 ebb_m365_contact_get_string_attribute (m365_contact, contact, mappings[ii].field_id, mappings[ii].m365_get_func);
1220 } else if (mappings[ii].get_func) {
1221 success = mappings[ii].get_func (bbm365, m365_contact, contact, mappings[ii].field_id, cnc, cancellable, error);
1222 }
1223 }
1224
1225 if (success)
1226 *out_object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1227 else
1228 g_clear_object (&contact);
1229
1230 return contact;
1231 }
1232
1233 static JsonBuilder *
ebb_m365_contact_to_json_locked(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,GCancellable * cancellable,GError ** error)1234 ebb_m365_contact_to_json_locked (EBookBackendM365 *bbm365,
1235 EContact *new_contact,
1236 EContact *old_contact, /* nullable */
1237 GCancellable *cancellable,
1238 GError **error)
1239 {
1240 JsonBuilder *builder;
1241 gint ii;
1242 gboolean success = TRUE;
1243
1244 g_return_val_if_fail (new_contact != NULL, NULL);
1245
1246 builder = json_builder_new_immutable ();
1247 e_m365_json_begin_object_member (builder, NULL);
1248
1249 for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1250 if (mappings[ii].m365_add_func) {
1251 ebb_m365_contact_add_string_attribute (new_contact, old_contact, mappings[ii].field_id, builder, mappings[ii].m365_add_func);
1252 } else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
1253 success = mappings[ii].add_func (bbm365, new_contact, old_contact, mappings[ii].field_id, NULL, builder, cancellable, error);
1254 }
1255 }
1256
1257 e_m365_json_end_object_member (builder);
1258
1259 if (!success)
1260 g_clear_object (&builder);
1261
1262 return builder;
1263 }
1264
1265 static gboolean
ebb_m365_contact_to_json_2nd_go_locked(EBookBackendM365 * bbm365,EContact * new_contact,EContact * old_contact,const gchar * m365_id,GCancellable * cancellable,GError ** error)1266 ebb_m365_contact_to_json_2nd_go_locked (EBookBackendM365 *bbm365,
1267 EContact *new_contact,
1268 EContact *old_contact, /* nullable */
1269 const gchar *m365_id,
1270 GCancellable *cancellable,
1271 GError **error)
1272 {
1273 gint ii;
1274 gboolean success = TRUE;
1275
1276 g_return_val_if_fail (new_contact != NULL, FALSE);
1277
1278 for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
1279 if (mappings[ii].add_in_second_go && mappings[ii].add_func) {
1280 success = mappings[ii].add_func (bbm365, new_contact, old_contact, mappings[ii].field_id, m365_id, NULL, cancellable, error);
1281 }
1282 }
1283
1284 return success;
1285 }
1286
1287 static void
ebb_m365_convert_error_to_client_error(GError ** perror)1288 ebb_m365_convert_error_to_client_error (GError **perror)
1289 {
1290 GError *error = NULL;
1291
1292 if (!perror || !*perror || (*perror)->domain == E_CLIENT_ERROR || (*perror)->domain == E_BOOK_CLIENT_ERROR)
1293 return;
1294
1295 /*if ((*perror)->domain == EWS_CONNECTION_ERROR) {
1296 switch ((*perror)->code) {
1297 case EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED:
1298 error = EC_ERROR_EX (E_CLIENT_ERROR_AUTHENTICATION_FAILED, (*perror)->message);
1299 break;
1300 case EWS_CONNECTION_ERROR_FOLDERNOTFOUND:
1301 case EWS_CONNECTION_ERROR_MANAGEDFOLDERNOTFOUND:
1302 case EWS_CONNECTION_ERROR_PARENTFOLDERNOTFOUND:
1303 case EWS_CONNECTION_ERROR_PUBLICFOLDERSERVERNOTFOUND:
1304 error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_NO_SUCH_BOOK, (*perror)->message);
1305 break;
1306 case EWS_CONNECTION_ERROR_EVENTNOTFOUND:
1307 case EWS_CONNECTION_ERROR_ITEMNOTFOUND:
1308 error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, (*perror)->message);
1309 break;
1310 case EWS_CONNECTION_ERROR_UNAVAILABLE:
1311 g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND, (*perror)->message);
1312 break;
1313 }
1314
1315 if (!error)
1316 error = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, (*perror)->message);
1317 }*/
1318
1319 if (error) {
1320 g_error_free (*perror);
1321 *perror = error;
1322 }
1323 }
1324
1325 static void
ebb_m365_maybe_disconnect_sync(EBookBackendM365 * bbm365,GError ** in_perror,GCancellable * cancellable)1326 ebb_m365_maybe_disconnect_sync (EBookBackendM365 *bbm365,
1327 GError **in_perror,
1328 GCancellable *cancellable)
1329 {
1330 g_return_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365));
1331
1332 if (in_perror && g_error_matches (*in_perror, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) {
1333 e_book_meta_backend_disconnect_sync (E_BOOK_META_BACKEND (bbm365), cancellable, NULL);
1334 e_backend_schedule_credentials_required (E_BACKEND (bbm365), E_SOURCE_CREDENTIALS_REASON_REJECTED, NULL, 0, NULL, NULL, G_STRFUNC);
1335 }
1336 }
1337
1338 static gboolean
ebb_m365_unset_connection_sync(EBookBackendM365 * bbm365,gboolean is_disconnect,GCancellable * cancellable,GError ** error)1339 ebb_m365_unset_connection_sync (EBookBackendM365 *bbm365,
1340 gboolean is_disconnect,
1341 GCancellable *cancellable,
1342 GError **error)
1343 {
1344 gboolean success = TRUE;
1345
1346 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365), FALSE);
1347
1348 LOCK (bbm365);
1349
1350 if (bbm365->priv->cnc) {
1351 if (is_disconnect)
1352 success = e_m365_connection_disconnect_sync (bbm365->priv->cnc, cancellable, error);
1353 }
1354
1355 g_clear_object (&bbm365->priv->cnc);
1356 g_clear_pointer (&bbm365->priv->folder_id, g_free);
1357
1358 UNLOCK (bbm365);
1359
1360 return success;
1361 }
1362
1363 static gboolean
ebb_m365_connect_sync(EBookMetaBackend * meta_backend,const ENamedParameters * credentials,ESourceAuthenticationResult * out_auth_result,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)1364 ebb_m365_connect_sync (EBookMetaBackend *meta_backend,
1365 const ENamedParameters *credentials,
1366 ESourceAuthenticationResult *out_auth_result,
1367 gchar **out_certificate_pem,
1368 GTlsCertificateFlags *out_certificate_errors,
1369 GCancellable *cancellable,
1370 GError **error)
1371 {
1372 EBookBackendM365 *bbm365;
1373 gboolean success = FALSE;
1374
1375 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1376 g_return_val_if_fail (out_auth_result != NULL, FALSE);
1377
1378 bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1379
1380 LOCK (bbm365);
1381
1382 if (bbm365->priv->cnc) {
1383 UNLOCK (bbm365);
1384
1385 *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
1386
1387 return TRUE;
1388 } else {
1389 EBackend *backend;
1390 ESourceRegistry *registry;
1391 ESource *source;
1392 EM365Connection *cnc = NULL;
1393 ESourceM365Folder *m365_folder_extension;
1394 CamelM365Settings *m365_settings;
1395 gchar *folder_id;
1396
1397 backend = E_BACKEND (bbm365);
1398 source = e_backend_get_source (backend);
1399 registry = e_book_backend_get_registry (E_BOOK_BACKEND (bbm365));
1400 m365_settings = camel_m365_settings_get_from_backend (backend, registry);
1401 g_warn_if_fail (m365_settings != NULL);
1402
1403 m365_folder_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
1404 folder_id = e_source_m365_folder_dup_id (m365_folder_extension);
1405
1406 if (folder_id) {
1407 cnc = e_m365_connection_new_for_backend (backend, registry, source, m365_settings);
1408
1409 *out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL, E_M365_FOLDER_KIND_CONTACTS, NULL, folder_id,
1410 out_certificate_pem, out_certificate_errors, cancellable, error);
1411
1412 if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
1413 bbm365->priv->cnc = g_object_ref (cnc);
1414
1415 g_warn_if_fail (bbm365->priv->folder_id == NULL);
1416
1417 g_free (bbm365->priv->folder_id);
1418 bbm365->priv->folder_id = folder_id;
1419
1420 folder_id = NULL;
1421 success = TRUE;
1422
1423 e_book_backend_set_writable (E_BOOK_BACKEND (bbm365), TRUE);
1424 }
1425 } else {
1426 *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
1427 g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Folder ID is not set")));
1428 }
1429
1430 g_clear_object (&cnc);
1431 g_free (folder_id);
1432 }
1433
1434 UNLOCK (bbm365);
1435
1436 ebb_m365_convert_error_to_client_error (error);
1437
1438 return success;
1439 }
1440
1441 static gboolean
ebb_m365_disconnect_sync(EBookMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)1442 ebb_m365_disconnect_sync (EBookMetaBackend *meta_backend,
1443 GCancellable *cancellable,
1444 GError **error)
1445 {
1446 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1447
1448 return ebb_m365_unset_connection_sync (E_BOOK_BACKEND_M365 (meta_backend), TRUE, cancellable, error);
1449 }
1450
1451 typedef struct _ObjectsDeltaData {
1452 EBookBackendM365 *bbm365;
1453 ECache *cache;
1454 GSList **out_created_objects;
1455 GSList **out_modified_objects;
1456 GSList **out_removed_objects;
1457 } ObjectsDeltaData;
1458
1459 static gboolean
ebb_m365_get_objects_delta_cb(EM365Connection * cnc,const GSList * results,gpointer user_data,GCancellable * cancellable,GError ** error)1460 ebb_m365_get_objects_delta_cb (EM365Connection *cnc,
1461 const GSList *results, /* JsonObject * - the returned objects from the server */
1462 gpointer user_data,
1463 GCancellable *cancellable,
1464 GError **error)
1465 {
1466 ObjectsDeltaData *odd = user_data;
1467 GSList *link;
1468
1469 g_return_val_if_fail (odd != NULL, FALSE);
1470
1471 for (link = (GSList *) results; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
1472 EM365Contact *contact = link->data;
1473 const gchar *id;
1474
1475 if (!contact)
1476 continue;
1477
1478 id = e_m365_contact_get_id (contact);
1479
1480 if (!id)
1481 continue;
1482
1483 if (e_m365_delta_is_removed_object (contact)) {
1484 *(odd->out_removed_objects) = g_slist_prepend (*(odd->out_removed_objects),
1485 e_book_meta_backend_info_new (id, NULL, NULL, NULL));
1486 } else {
1487 GSList **out_slist;
1488 EContact *vcard;
1489 gchar *object;
1490
1491 if (e_cache_contains (odd->cache, id, E_CACHE_INCLUDE_DELETED))
1492 out_slist = odd->out_modified_objects;
1493 else
1494 out_slist = odd->out_created_objects;
1495
1496 vcard = ebb_m365_json_contact_to_vcard (odd->bbm365, contact, cnc, &object, cancellable, error);
1497
1498 g_clear_object (&vcard);
1499
1500 if (!g_cancellable_is_cancelled (cancellable))
1501 g_warn_if_fail (object != NULL);
1502
1503 if (object) {
1504 EBookMetaBackendInfo *nfo;
1505
1506 nfo = e_book_meta_backend_info_new (id,
1507 e_m365_contact_get_change_key (contact),
1508 object, NULL);
1509
1510 nfo->extra = object; /* assumes ownership, to avoid unnecessary re-allocation */
1511
1512 *out_slist = g_slist_prepend (*out_slist, nfo);
1513 }
1514 }
1515 }
1516
1517 return TRUE;
1518 }
1519
1520 static gboolean
ebb_m365_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)1521 ebb_m365_get_changes_sync (EBookMetaBackend *meta_backend,
1522 const gchar *last_sync_tag,
1523 gboolean is_repeat,
1524 gchar **out_new_sync_tag,
1525 gboolean *out_repeat,
1526 GSList **out_created_objects,
1527 GSList **out_modified_objects,
1528 GSList **out_removed_objects,
1529 GCancellable *cancellable,
1530 GError **error)
1531 {
1532 EBookBackendM365 *bbm365;
1533 EBookCache *book_cache;
1534 ObjectsDeltaData odd;
1535 gboolean success;
1536 GError *local_error = NULL;
1537
1538 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1539 g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
1540 g_return_val_if_fail (out_repeat != NULL, FALSE);
1541 g_return_val_if_fail (out_created_objects != NULL, FALSE);
1542 g_return_val_if_fail (out_modified_objects != NULL, FALSE);
1543 g_return_val_if_fail (out_removed_objects != NULL, FALSE);
1544
1545 *out_created_objects = NULL;
1546 *out_modified_objects = NULL;
1547 *out_removed_objects = NULL;
1548
1549 bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1550
1551 book_cache = e_book_meta_backend_ref_cache (meta_backend);
1552 g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
1553
1554 odd.bbm365 = bbm365;
1555 odd.cache = E_CACHE (book_cache);
1556 odd.out_created_objects = out_created_objects;
1557 odd.out_modified_objects = out_modified_objects;
1558 odd.out_removed_objects = out_removed_objects;
1559
1560 LOCK (bbm365);
1561
1562 success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
1563 E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, last_sync_tag, 0,
1564 ebb_m365_get_objects_delta_cb, &odd,
1565 out_new_sync_tag, cancellable, &local_error);
1566
1567 if (e_m365_connection_util_delta_token_failed (local_error)) {
1568 GSList *known_uids = NULL, *link;
1569
1570 g_clear_error (&local_error);
1571
1572 if (e_book_cache_search_uids (book_cache, NULL, &known_uids, cancellable, error)) {
1573 for (link = known_uids; link; link = g_slist_next (link)) {
1574 const gchar *uid = link->data;
1575
1576 if (uid) {
1577 *out_removed_objects = g_slist_prepend (*out_removed_objects,
1578 e_book_meta_backend_info_new (uid, NULL, NULL, NULL));
1579 }
1580 }
1581 }
1582
1583 e_cache_remove_all (E_CACHE (book_cache), cancellable, NULL);
1584
1585 g_slist_free_full (known_uids, g_free);
1586
1587 success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
1588 E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, NULL, 0,
1589 ebb_m365_get_objects_delta_cb, &odd,
1590 out_new_sync_tag, cancellable, &local_error);
1591 } else if (local_error) {
1592 g_propagate_error (error, local_error);
1593 }
1594
1595 UNLOCK (bbm365);
1596
1597 ebb_m365_convert_error_to_client_error (error);
1598 ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1599
1600 g_clear_object (&book_cache);
1601
1602 return success;
1603 }
1604
1605 static gboolean
ebb_m365_load_contact_sync(EBookMetaBackend * meta_backend,const gchar * uid,const gchar * extra,EContact ** out_contact,gchar ** out_extra,GCancellable * cancellable,GError ** error)1606 ebb_m365_load_contact_sync (EBookMetaBackend *meta_backend,
1607 const gchar *uid,
1608 const gchar *extra,
1609 EContact **out_contact,
1610 gchar **out_extra,
1611 GCancellable *cancellable,
1612 GError **error)
1613 {
1614 EBookBackendM365 *bbm365;
1615 EM365Contact *contact = NULL;
1616 gboolean success;
1617
1618 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1619 g_return_val_if_fail (uid != NULL, FALSE);
1620 g_return_val_if_fail (out_contact, FALSE);
1621 g_return_val_if_fail (out_extra != NULL, FALSE);
1622
1623 bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1624
1625 LOCK (bbm365);
1626
1627 success = e_m365_connection_get_contact_sync (bbm365->priv->cnc, NULL,
1628 bbm365->priv->folder_id, uid, &contact, cancellable, error);
1629
1630 if (success) {
1631 *out_contact = ebb_m365_json_contact_to_vcard (bbm365, contact, bbm365->priv->cnc, out_extra, cancellable, error);
1632
1633 if (contact)
1634 json_object_unref (contact);
1635 }
1636
1637 UNLOCK (bbm365);
1638
1639 ebb_m365_convert_error_to_client_error (error);
1640 ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1641
1642 return success;
1643 }
1644
1645 static gboolean
ebb_m365_save_contact_sync(EBookMetaBackend * meta_backend,gboolean overwrite_existing,EConflictResolution conflict_resolution,EContact * contact,const gchar * extra,guint32 opflags,gchar ** out_new_uid,gchar ** out_new_extra,GCancellable * cancellable,GError ** error)1646 ebb_m365_save_contact_sync (EBookMetaBackend *meta_backend,
1647 gboolean overwrite_existing,
1648 EConflictResolution conflict_resolution,
1649 /* const */ EContact *contact,
1650 const gchar *extra,
1651 guint32 opflags,
1652 gchar **out_new_uid,
1653 gchar **out_new_extra,
1654 GCancellable *cancellable,
1655 GError **error)
1656 {
1657 EBookBackendM365 *bbm365;
1658 EContact *tmp_contact = NULL, *old_contact = NULL;
1659 JsonBuilder *builder;
1660 gboolean success = FALSE;
1661
1662 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1663 g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1664 g_return_val_if_fail (out_new_uid != NULL, FALSE);
1665 g_return_val_if_fail (out_new_extra != NULL, FALSE);
1666
1667 if (GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST))) {
1668 g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED, _("Cannot save contact list into a Microsoft 365 address book")));
1669 return FALSE;
1670 }
1671
1672 bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1673
1674 LOCK (bbm365);
1675
1676 if (e_vcard_get_attribute (E_VCARD (contact), EVC_PHOTO)) {
1677 tmp_contact = e_contact_duplicate (contact);
1678 contact = tmp_contact;
1679
1680 e_contact_inline_local_photos (contact, NULL);
1681 }
1682
1683 if (extra && *extra)
1684 old_contact = e_contact_new_from_vcard (extra);
1685
1686 builder = ebb_m365_contact_to_json_locked (bbm365, contact, old_contact, cancellable, error);
1687
1688 if (builder) {
1689 if (overwrite_existing) {
1690 const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
1691
1692 success = e_m365_connection_update_contact_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1693 uid, builder, cancellable, error);
1694
1695 if (success)
1696 success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact, old_contact, uid, cancellable, error);
1697
1698 if (success)
1699 *out_new_extra = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
1700 } else {
1701 EM365Contact *created_contact = NULL;
1702
1703 success = e_m365_connection_create_contact_sync (bbm365->priv->cnc, NULL, bbm365->priv->folder_id,
1704 builder, &created_contact, cancellable, error);
1705
1706 if (success && created_contact) {
1707 const gchar *m365_id = e_m365_contact_get_id (created_contact);
1708
1709 success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact, old_contact, m365_id, cancellable, error);
1710 }
1711
1712 if (success && created_contact) {
1713 EContact *vcard;
1714
1715 *out_new_uid = g_strdup (e_m365_contact_get_id (created_contact));
1716
1717 vcard = ebb_m365_json_contact_to_vcard (bbm365, created_contact, bbm365->priv->cnc, out_new_extra, cancellable, error);
1718
1719 if (!vcard)
1720 success = FALSE;
1721
1722 g_clear_object (&vcard);
1723 }
1724
1725 if (created_contact)
1726 json_object_unref (created_contact);
1727 }
1728
1729 g_clear_object (&builder);
1730 }
1731
1732 UNLOCK (bbm365);
1733
1734 ebb_m365_convert_error_to_client_error (error);
1735 ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1736
1737 g_clear_object (&old_contact);
1738 g_clear_object (&tmp_contact);
1739
1740 return success;
1741 }
1742
1743 static gboolean
ebb_m365_remove_contact_sync(EBookMetaBackend * meta_backend,EConflictResolution conflict_resolution,const gchar * uid,const gchar * extra,const gchar * object,guint32 opflags,GCancellable * cancellable,GError ** error)1744 ebb_m365_remove_contact_sync (EBookMetaBackend *meta_backend,
1745 EConflictResolution conflict_resolution,
1746 const gchar *uid,
1747 const gchar *extra,
1748 const gchar *object,
1749 guint32 opflags,
1750 GCancellable *cancellable,
1751 GError **error)
1752 {
1753 EBookBackendM365 *bbm365;
1754 gboolean success;
1755
1756 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1757
1758 bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
1759
1760 LOCK (bbm365);
1761
1762 success = e_m365_connection_delete_contact_sync (bbm365->priv->cnc, NULL,
1763 bbm365->priv->folder_id, uid, cancellable, error);
1764
1765 UNLOCK (bbm365);
1766
1767 ebb_m365_convert_error_to_client_error (error);
1768 ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
1769
1770 return success;
1771 }
1772
1773 static gboolean
ebb_m365_search_sync(EBookMetaBackend * meta_backend,const gchar * expr,gboolean meta_contact,GSList ** out_contacts,GCancellable * cancellable,GError ** error)1774 ebb_m365_search_sync (EBookMetaBackend *meta_backend,
1775 const gchar *expr,
1776 gboolean meta_contact,
1777 GSList **out_contacts,
1778 GCancellable *cancellable,
1779 GError **error)
1780 {
1781 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1782
1783 /* Ignore errors, just try its best */
1784 /*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable, NULL);*/
1785
1786 /* Chain up to parent's method */
1787 return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_sync (meta_backend, expr, meta_contact, out_contacts, cancellable, error);
1788 }
1789
1790 static gboolean
ebb_m365_search_uids_sync(EBookMetaBackend * meta_backend,const gchar * expr,GSList ** out_uids,GCancellable * cancellable,GError ** error)1791 ebb_m365_search_uids_sync (EBookMetaBackend *meta_backend,
1792 const gchar *expr,
1793 GSList **out_uids,
1794 GCancellable *cancellable,
1795 GError **error)
1796 {
1797 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
1798
1799 /* Ignore errors, just try its best */
1800 /*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable, NULL);*/
1801
1802 /* Chain up to parent's method */
1803 return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_uids_sync (meta_backend, expr,
1804 out_uids, cancellable, error);
1805 }
1806
1807 static gchar *
ebb_m365_get_backend_property(EBookBackend * book_backend,const gchar * prop_name)1808 ebb_m365_get_backend_property (EBookBackend *book_backend,
1809 const gchar *prop_name)
1810 {
1811 g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (book_backend), NULL);
1812 g_return_val_if_fail (prop_name != NULL, NULL);
1813
1814 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
1815 return g_strjoin (",",
1816 "net",
1817 "contact-lists",
1818 "do-initial-query",
1819 e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)),
1820 NULL);
1821 } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
1822 return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
1823 } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
1824 GString *buffer;
1825 gchar *fields;
1826 gint ii;
1827
1828 buffer = g_string_sized_new (1024);
1829
1830 for (ii = 0; ii < G_N_ELEMENTS (mappings); ii++) {
1831 if (buffer->len > 0)
1832 g_string_append_c (buffer, ',');
1833
1834 g_string_append (buffer, e_contact_field_name (mappings[ii].field_id));
1835 }
1836
1837 fields = g_strjoin (
1838 ",",
1839 buffer->str,
1840 e_contact_field_name (E_CONTACT_FULL_NAME),
1841 e_contact_field_name (E_CONTACT_EMAIL_1),
1842 e_contact_field_name (E_CONTACT_EMAIL_2),
1843 e_contact_field_name (E_CONTACT_EMAIL_3),
1844 e_contact_field_name (E_CONTACT_EMAIL_4),
1845 NULL);
1846
1847 g_string_free (buffer, TRUE);
1848
1849 return fields;
1850 }
1851
1852 /* Chain up to parent's method. */
1853 return E_BOOK_BACKEND_CLASS (e_book_backend_m365_parent_class)->impl_get_backend_property (book_backend, prop_name);
1854 }
1855
1856 static gboolean
ebb_m365_get_destination_address(EBackend * backend,gchar ** host,guint16 * port)1857 ebb_m365_get_destination_address (EBackend *backend,
1858 gchar **host,
1859 guint16 *port)
1860 {
1861 g_return_val_if_fail (port != NULL, FALSE);
1862 g_return_val_if_fail (host != NULL, FALSE);
1863
1864 /* Sanity checking */
1865 if (!e_book_backend_get_registry (E_BOOK_BACKEND (backend)) ||
1866 !e_backend_get_source (backend))
1867 return FALSE;
1868
1869 *host = g_strdup ("graph.microsoft.com");
1870 *port = 443;
1871
1872 return TRUE;
1873 }
1874
1875 static void
e_book_backend_m365_dispose(GObject * object)1876 e_book_backend_m365_dispose (GObject *object)
1877 {
1878 EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
1879
1880 ebb_m365_unset_connection_sync (bbm365, FALSE, NULL, NULL);
1881
1882 /* Chain up to parent's method. */
1883 G_OBJECT_CLASS (e_book_backend_m365_parent_class)->dispose (object);
1884 }
1885
1886 static void
e_book_backend_m365_finalize(GObject * object)1887 e_book_backend_m365_finalize (GObject *object)
1888 {
1889 EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
1890
1891 g_rec_mutex_clear (&bbm365->priv->property_lock);
1892
1893 /* Chain up to parent's method. */
1894 G_OBJECT_CLASS (e_book_backend_m365_parent_class)->finalize (object);
1895 }
1896
1897 static void
e_book_backend_m365_init(EBookBackendM365 * bbm365)1898 e_book_backend_m365_init (EBookBackendM365 *bbm365)
1899 {
1900 bbm365->priv = e_book_backend_m365_get_instance_private (bbm365);
1901
1902 g_rec_mutex_init (&bbm365->priv->property_lock);
1903 }
1904
1905 static void
e_book_backend_m365_class_init(EBookBackendM365Class * klass)1906 e_book_backend_m365_class_init (EBookBackendM365Class *klass)
1907 {
1908 GObjectClass *object_class;
1909 EBackendClass *backend_class;
1910 EBookBackendClass *book_backend_class;
1911 EBookMetaBackendClass *book_meta_backend_class;
1912
1913 book_meta_backend_class = E_BOOK_META_BACKEND_CLASS (klass);
1914 book_meta_backend_class->backend_module_filename = "libebookbackendmicrosoft365.so";
1915 book_meta_backend_class->backend_factory_type_name = "EBookBackendM365Factory";
1916 book_meta_backend_class->connect_sync = ebb_m365_connect_sync;
1917 book_meta_backend_class->disconnect_sync = ebb_m365_disconnect_sync;
1918 book_meta_backend_class->get_changes_sync = ebb_m365_get_changes_sync;
1919 book_meta_backend_class->load_contact_sync = ebb_m365_load_contact_sync;
1920 book_meta_backend_class->save_contact_sync = ebb_m365_save_contact_sync;
1921 book_meta_backend_class->remove_contact_sync = ebb_m365_remove_contact_sync;
1922 book_meta_backend_class->search_sync = ebb_m365_search_sync;
1923 book_meta_backend_class->search_uids_sync = ebb_m365_search_uids_sync;
1924
1925 book_backend_class = E_BOOK_BACKEND_CLASS (klass);
1926 book_backend_class->impl_get_backend_property = ebb_m365_get_backend_property;
1927
1928 backend_class = E_BACKEND_CLASS (klass);
1929 backend_class->get_destination_address = ebb_m365_get_destination_address;
1930
1931 object_class = G_OBJECT_CLASS (klass);
1932 object_class->dispose = e_book_backend_m365_dispose;
1933 object_class->finalize = e_book_backend_m365_finalize;
1934 }
1935