[evolution-ews] Add (disabled by default) Microsoft 365 (Graph API) connector
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-ews] Add (disabled by default) Microsoft 365 (Graph API) connector
- Date: Wed, 5 Aug 2020 06:57:43 +0000 (UTC)
commit 73f722fffe7c932a040ca053c4299bc572018d4c
Author: Milan Crha <mcrha redhat com>
Date: Wed Aug 5 08:44:31 2020 +0200
Add (disabled by default) Microsoft 365 (Graph API) connector
To enable the connector users need to export ENABLE_M365=1 environment
variable in a way that it's known both to the Evolution itself and
to the evolution-data-server background services. One such place can
be in /etc/environment.
It's disabled by default, because the Microsoft Graph API is currently
far from being feature complete with the EWS API, thus it's here only
as kind of a preview.
CMakeLists.txt | 25 +
config.h.in | 12 +
po/POTFILES.in | 75 +-
src/CMakeLists.txt | 66 +-
src/EWS/CMakeLists.txt | 64 +
src/{ => EWS}/addressbook/CMakeLists.txt | 4 +-
.../addressbook/e-book-backend-ews-factory.c | 4 +-
src/{ => EWS}/addressbook/e-book-backend-ews.c | 14 +-
src/{ => EWS}/addressbook/e-book-backend-ews.h | 0
src/{ => EWS}/addressbook/ews-oab-decoder.c | 0
src/{ => EWS}/addressbook/ews-oab-decoder.h | 0
src/{ => EWS}/addressbook/ews-oab-decompress.c | 0
src/{ => EWS}/addressbook/ews-oab-decompress.h | 0
src/{ => EWS}/addressbook/ews-oab-props.h | 0
.../addressbook/gal-lzx-decompress-test.c | 0
src/{ => EWS}/addressbook/mspack/README.mspack | 0
src/{ => EWS}/addressbook/mspack/lzx.h | 0
src/{ => EWS}/addressbook/mspack/lzxd.c | 0
src/{ => EWS}/addressbook/mspack/oab-decompress.c | 0
src/{ => EWS}/addressbook/mspack/readbits.h | 0
src/{ => EWS}/addressbook/mspack/readhuff.h | 0
src/{ => EWS}/addressbook/oab-decode-test.c | 0
src/{ => EWS}/calendar/CMakeLists.txt | 4 +-
src/{ => EWS}/calendar/e-cal-backend-ews-factory.c | 4 +-
src/{ => EWS}/calendar/e-cal-backend-ews-utils.c | 8 +-
src/{ => EWS}/calendar/e-cal-backend-ews-utils.h | 4 +-
src/{ => EWS}/calendar/e-cal-backend-ews.c | 8 +-
src/{ => EWS}/calendar/e-cal-backend-ews.h | 2 +-
src/{ => EWS}/calendar/windowsZones.xml | 0
src/{ => EWS}/camel/CMakeLists.txt | 8 +-
src/{ => EWS}/camel/camel-ews-enums.h | 0
src/{ => EWS}/camel/camel-ews-folder.c | 10 +-
src/{ => EWS}/camel/camel-ews-folder.h | 0
src/{ => EWS}/camel/camel-ews-message-info.c | 0
src/{ => EWS}/camel/camel-ews-message-info.h | 0
src/{ => EWS}/camel/camel-ews-private.h | 0
src/{ => EWS}/camel/camel-ews-provider.c | 2 +-
src/{ => EWS}/camel/camel-ews-search.c | 2 +-
src/{ => EWS}/camel/camel-ews-search.h | 0
src/{ => EWS}/camel/camel-ews-store-summary.c | 2 +-
src/{ => EWS}/camel/camel-ews-store-summary.h | 2 +-
src/{ => EWS}/camel/camel-ews-store.c | 8 +-
src/{ => EWS}/camel/camel-ews-store.h | 2 +-
src/{ => EWS}/camel/camel-ews-summary.c | 0
src/{ => EWS}/camel/camel-ews-summary.h | 0
src/{ => EWS}/camel/camel-ews-transport.c | 4 +-
src/{ => EWS}/camel/camel-ews-transport.h | 0
src/{ => EWS}/camel/camel-ews-utils.c | 8 +-
src/{ => EWS}/camel/camel-ews-utils.h | 2 +-
src/{ => EWS}/camel/libcamelews.urls | 0
src/{server => EWS/common}/CMakeLists.txt | 4 +-
src/{server => EWS/common}/camel-ews-settings.c | 0
src/{server => EWS/common}/camel-ews-settings.h | 0
.../common}/camel-sasl-xoauth2-office365.c | 0
.../common}/camel-sasl-xoauth2-office365.h | 0
src/{server => EWS/common}/e-ews-calendar-utils.c | 0
src/{server => EWS/common}/e-ews-calendar-utils.h | 4 +-
src/{server => EWS/common}/e-ews-camel-common.c | 4 +-
src/{server => EWS/common}/e-ews-camel-common.h | 4 +-
.../common}/e-ews-connection-utils.c | 0
.../common}/e-ews-connection-utils.h | 2 +-
src/{server => EWS/common}/e-ews-connection.c | 0
src/{server => EWS/common}/e-ews-connection.h | 0
src/{server => EWS/common}/e-ews-debug.c | 0
src/{server => EWS/common}/e-ews-debug.h | 2 +-
src/{server => EWS/common}/e-ews-enums.h | 0
src/{server => EWS/common}/e-ews-folder.c | 0
src/{server => EWS/common}/e-ews-folder.h | 0
src/{server => EWS/common}/e-ews-item-change.c | 0
src/{server => EWS/common}/e-ews-item-change.h | 0
src/{server => EWS/common}/e-ews-item.c | 0
src/{server => EWS/common}/e-ews-item.h | 0
src/{server => EWS/common}/e-ews-message.c | 0
src/{server => EWS/common}/e-ews-message.h | 0
src/{server => EWS/common}/e-ews-notification.c | 0
src/{server => EWS/common}/e-ews-notification.h | 0
src/{server => EWS/common}/e-ews-oof-settings.c | 0
src/{server => EWS/common}/e-ews-oof-settings.h | 4 +-
.../common}/e-ews-query-to-restriction.c | 2 +-
.../common}/e-ews-query-to-restriction.h | 4 +-
.../common}/e-oauth2-service-office365.c | 2 +-
.../common}/e-oauth2-service-office365.h | 0
src/{server => EWS/common}/e-soap-message.c | 0
src/{server => EWS/common}/e-soap-message.h | 0
src/{server => EWS/common}/e-soap-response.c | 0
src/{server => EWS/common}/e-soap-response.h | 0
src/{server => EWS/common}/e-soup-auth-negotiate.c | 0
src/{server => EWS/common}/e-soup-auth-negotiate.h | 0
src/{server => EWS/common}/e-source-ews-folder.c | 0
src/{server => EWS/common}/e-source-ews-folder.h | 2 +-
src/{server => EWS/common}/ews-errors.c | 0
src/{server => EWS/common}/ews-errors.h | 2 +-
.../evolution}/CMakeLists.txt | 0
.../evolution}/e-book-config-ews.c | 4 +-
.../evolution}/e-book-config-ews.h | 0
.../evolution}/e-cal-config-ews.c | 0
.../evolution}/e-cal-config-ews.h | 0
.../evolution}/e-ews-config-lookup.c | 4 +-
.../evolution}/e-ews-config-lookup.h | 0
.../evolution}/e-ews-config-ui-extension.c | 0
.../evolution}/e-ews-config-ui-extension.h | 0
.../evolution}/e-ews-config-utils.c | 6 +-
.../evolution}/e-ews-config-utils.h | 2 +-
.../evolution}/e-ews-edit-folder-permissions.c | 0
.../evolution}/e-ews-edit-folder-permissions.h | 6 +-
.../evolution}/e-ews-ooo-notificator.c | 2 +-
.../evolution}/e-ews-ooo-notificator.h | 0
.../evolution}/e-ews-photo-source.c | 4 +-
.../evolution}/e-ews-photo-source.h | 0
.../evolution}/e-ews-search-user.c | 0
.../evolution}/e-ews-search-user.h | 4 +-
.../evolution}/e-ews-subscribe-foreign-folder.c | 2 +-
.../evolution}/e-ews-subscribe-foreign-folder.h | 0
.../evolution}/e-mail-config-ews-autodiscover.c | 4 +-
.../evolution}/e-mail-config-ews-autodiscover.h | 0
.../evolution}/e-mail-config-ews-backend.c | 2 +-
.../evolution}/e-mail-config-ews-backend.h | 0
.../evolution}/e-mail-config-ews-delegates-page.c | 8 +-
.../evolution}/e-mail-config-ews-delegates-page.h | 0
.../e-mail-config-ews-folder-sizes-page.c | 0
.../e-mail-config-ews-folder-sizes-page.h | 0
.../evolution}/e-mail-config-ews-gal.c | 2 +-
.../evolution}/e-mail-config-ews-gal.h | 0
.../evolution}/e-mail-config-ews-notebook.c | 0
.../evolution}/e-mail-config-ews-notebook.h | 0
.../evolution}/e-mail-config-ews-oal-combo-box.c | 4 +-
.../evolution}/e-mail-config-ews-oal-combo-box.h | 0
.../evolution}/e-mail-config-ews-offline-options.c | 0
.../evolution}/e-mail-config-ews-offline-options.h | 0
.../evolution}/e-mail-config-ews-ooo-page.c | 8 +-
.../evolution}/e-mail-config-ews-ooo-page.h | 0
.../evolution}/module-ews-configuration.c | 6 +-
.../evolution}/module-ews-configuration.error.xml | 0
src/{collection => EWS/registry}/CMakeLists.txt | 0
.../registry}/e-ews-backend-factory.c | 0
.../registry}/e-ews-backend-factory.h | 0
src/{collection => EWS/registry}/e-ews-backend.c | 4 +-
src/{collection => EWS/registry}/e-ews-backend.h | 2 +-
.../registry}/module-ews-backend.c | 4 +-
src/Microsoft365/CMakeLists.txt | 64 +
src/Microsoft365/addressbook/CMakeLists.txt | 65 +
.../addressbook/e-book-backend-m365-factory.c | 74 +
src/Microsoft365/addressbook/e-book-backend-m365.c | 1933 +++++++
src/Microsoft365/addressbook/e-book-backend-m365.h | 32 +
src/Microsoft365/calendar/CMakeLists.txt | 61 +
.../calendar/e-cal-backend-m365-factory.c | 139 +
src/Microsoft365/calendar/e-cal-backend-m365.c | 3710 ++++++++++++
src/Microsoft365/calendar/e-cal-backend-m365.h | 37 +
src/Microsoft365/camel/CMakeLists.txt | 75 +
src/Microsoft365/camel/camel-m365-folder-summary.c | 306 +
src/Microsoft365/camel/camel-m365-folder-summary.h | 72 +
src/Microsoft365/camel/camel-m365-folder.c | 1847 ++++++
src/Microsoft365/camel/camel-m365-folder.h | 60 +
src/Microsoft365/camel/camel-m365-message-info.c | 409 ++
src/Microsoft365/camel/camel-m365-message-info.h | 70 +
src/Microsoft365/camel/camel-m365-provider.c | 122 +
src/Microsoft365/camel/camel-m365-store-summary.c | 1439 +++++
src/Microsoft365/camel/camel-m365-store-summary.h | 189 +
src/Microsoft365/camel/camel-m365-store.c | 1831 ++++++
src/Microsoft365/camel/camel-m365-store.h | 70 +
src/Microsoft365/camel/camel-m365-transport.c | 400 ++
src/Microsoft365/camel/camel-m365-transport.h | 50 +
src/Microsoft365/camel/camel-m365-utils.c | 1004 ++++
src/Microsoft365/camel/camel-m365-utils.h | 48 +
src/Microsoft365/camel/libcamelmicrosoft365.urls | 1 +
src/Microsoft365/common/CMakeLists.txt | 72 +
src/Microsoft365/common/camel-m365-settings.c | 938 +++
src/Microsoft365/common/camel-m365-settings.h | 126 +
.../common/camel-sasl-xoauth2-microsoft365.c | 45 +
.../common/camel-sasl-xoauth2-microsoft365.h | 54 +
src/Microsoft365/common/e-m365-connection.c | 6007 ++++++++++++++++++++
src/Microsoft365/common/e-m365-connection.h | 758 +++
src/Microsoft365/common/e-m365-enums.h | 26 +
src/Microsoft365/common/e-m365-json-utils.c | 3889 +++++++++++++
src/Microsoft365/common/e-m365-json-utils.h | 1056 ++++
src/Microsoft365/common/e-m365-tz-utils.c | 184 +
src/Microsoft365/common/e-m365-tz-utils.h | 21 +
.../common/e-oauth2-service-microsoft365.c | 374 ++
.../common/e-oauth2-service-microsoft365.h | 54 +
src/Microsoft365/common/e-source-m365-folder.c | 287 +
src/Microsoft365/common/e-source-m365-folder.h | 70 +
src/Microsoft365/evolution/CMakeLists.txt | 39 +
src/Microsoft365/evolution/e-book-config-m365.c | 66 +
src/Microsoft365/evolution/e-book-config-m365.h | 51 +
src/Microsoft365/evolution/e-cal-config-m365.c | 84 +
src/Microsoft365/evolution/e-cal-config-m365.h | 51 +
.../evolution/e-mail-config-m365-backend.c | 492 ++
.../evolution/e-mail-config-m365-backend.h | 53 +
.../evolution/module-m365-configuration.c | 41 +
src/Microsoft365/registry/CMakeLists.txt | 24 +
src/Microsoft365/registry/e-m365-backend-factory.c | 78 +
src/Microsoft365/registry/e-m365-backend-factory.h | 52 +
src/Microsoft365/registry/e-m365-backend.c | 955 ++++
src/Microsoft365/registry/e-m365-backend.h | 51 +
src/Microsoft365/registry/e-source-m365-deltas.c | 155 +
src/Microsoft365/registry/e-source-m365-deltas.h | 61 +
src/Microsoft365/registry/module-m365-backend.c | 41 +
tests/CMakeLists.txt | 4 +-
tests/ews-test-camel.c | 6 +-
tests/ews-test-common.h | 4 +-
tests/ews-test-timezones.c | 4 +-
201 files changed, 30626 insertions(+), 213 deletions(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fc2571a7..2791a9c8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@ set(eds_minimum_version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJ
set(evo_minimum_version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
set(mspack_minimum_version 0.4)
set(libical_glib_minimum_version 3.0.5)
+set(json_glib_minimum_version 1.0.4)
# Load modules from the source tree
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
@@ -142,6 +143,8 @@ pkg_check_modules(SOUP REQUIRED libsoup-2.4>=${soup_minimum_version})
pkg_check_modules(LIBICAL_GLIB REQUIRED libical-glib>=${libical_glib_minimum_version})
+pkg_check_modules(JSON_GLIB REQUIRED json-glib-1.0>=${json_glib_minimum_version})
+
# ******************************
# libmspack with OAB support
# ******************************
@@ -197,6 +200,28 @@ if(WITH_OFFICE365_REDIRECT_URI STREQUAL "")
set(WITH_OFFICE365_REDIRECT_URI "https://login.microsoftonline.com/common/oauth2/nativeclient")
endif(WITH_OFFICE365_REDIRECT_URI STREQUAL "")
+# ******************************
+# Microsoft365 OAuth2 support (v2.0)
+# ******************************
+
+add_printable_variable(WITH_MICROSOFT365_TENANT "Microsoft 365 OAuth 2.0 tenant" "")
+
+if(WITH_MICROSOFT365_TENANT STREQUAL "")
+ set(WITH_MICROSOFT365_TENANT "common")
+endif(WITH_MICROSOFT365_TENANT STREQUAL "")
+
+add_printable_variable(WITH_MICROSOFT365_CLIENT_ID "Microsoft 365 OAuth 2.0 client ID" "")
+
+if(WITH_MICROSOFT365_CLIENT_ID STREQUAL "")
+ set(WITH_MICROSOFT365_CLIENT_ID "20460e5d-ce91-49af-a3a5-70b6be7486d1")
+endif(WITH_MICROSOFT365_CLIENT_ID STREQUAL "")
+
+add_printable_variable(WITH_MICROSOFT365_REDIRECT_URI "Microsoft 365 OAuth 2.0 redirect URI" "")
+
+if(WITH_MICROSOFT365_REDIRECT_URI STREQUAL "")
+ set(WITH_MICROSOFT365_REDIRECT_URI "https://login.microsoftonline.com/common/oauth2/nativeclient")
+endif(WITH_MICROSOFT365_REDIRECT_URI STREQUAL "")
+
# ******************************
# Special directories
# ******************************
diff --git a/config.h.in b/config.h.in
index ad3f60c7..ae8ae20e 100644
--- a/config.h.in
+++ b/config.h.in
@@ -12,6 +12,9 @@
/* Package name for gettext */
#define GETTEXT_PACKAGE "@GETTEXT_PACKAGE@"
+/* Configured with enabled maintainer mode */
+#cmakedefine ENABLE_MAINTAINER_MODE 1
+
/* Define to add support for low level tests */
#cmakedefine ENABLE_TESTS 1
@@ -29,3 +32,12 @@
/* Define Office365 OAuth 2.0 default Redirect URI to use */
#define OFFICE365_REDIRECT_URI "@WITH_OFFICE365_REDIRECT_URI@"
+
+/* Define Microsoft 365 OAuth 2.0 default Tenant to use */
+#define MICROSOFT365_TENANT "@WITH_MICROSOFT365_TENANT@"
+
+/* Define Microsoft 365 OAuth 2.0 default Client ID to use */
+#define MICROSOFT365_CLIENT_ID "@WITH_MICROSOFT365_CLIENT_ID@"
+
+/* Define Microsoft 365 OAuth 2.0 default Redirect URI to use */
+#define MICROSOFT365_REDIRECT_URI "@WITH_MICROSOFT365_REDIRECT_URI@"
diff --git a/po/POTFILES.in b/po/POTFILES.in
index fd153dd1..2a89548f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,35 +2,46 @@
# Please keep this list in alphabetic order.
[encoding: UTF-8]
org.gnome.Evolution-ews.metainfo.xml.in
-src/addressbook/e-book-backend-ews.c
-src/calendar/e-cal-backend-ews.c
-src/calendar/e-cal-backend-ews-utils.c
-src/camel/camel-ews-folder.c
-src/camel/camel-ews-provider.c
-src/camel/camel-ews-store.c
-src/camel/camel-ews-store.h
-src/camel/camel-ews-transport.c
-src/camel/camel-ews-utils.c
-src/collection/e-ews-backend.c
-src/collection/module-ews-backend.c
-src/configuration/e-book-config-ews.c
-src/configuration/e-ews-config-lookup.c
-src/configuration/e-ews-config-utils.c
-src/configuration/e-ews-edit-folder-permissions.c
-src/configuration/e-ews-ooo-notificator.c
-src/configuration/e-ews-search-user.c
-src/configuration/e-ews-subscribe-foreign-folder.c
-src/configuration/e-mail-config-ews-autodiscover.c
-src/configuration/e-mail-config-ews-backend.c
-src/configuration/e-mail-config-ews-delegates-page.c
-src/configuration/e-mail-config-ews-folder-sizes-page.c
-src/configuration/e-mail-config-ews-gal.c
-src/configuration/e-mail-config-ews-ooo-page.c
-src/configuration/module-ews-configuration.error.xml
-src/server/camel-sasl-xoauth2-office365.c
-src/server/e-ews-calendar-utils.c
-src/server/e-ews-camel-common.c
-src/server/e-ews-connection.c
-src/server/e-ews-connection-utils.c
-src/server/e-ews-folder.c
-src/server/e-oauth2-service-office365.c
+src/EWS/addressbook/e-book-backend-ews.c
+src/EWS/calendar/e-cal-backend-ews.c
+src/EWS/calendar/e-cal-backend-ews-utils.c
+src/EWS/camel/camel-ews-folder.c
+src/EWS/camel/camel-ews-provider.c
+src/EWS/camel/camel-ews-store.c
+src/EWS/camel/camel-ews-store.h
+src/EWS/camel/camel-ews-transport.c
+src/EWS/camel/camel-ews-utils.c
+src/EWS/common/camel-sasl-xoauth2-office365.c
+src/EWS/common/e-ews-calendar-utils.c
+src/EWS/common/e-ews-camel-common.c
+src/EWS/common/e-ews-connection.c
+src/EWS/common/e-ews-connection-utils.c
+src/EWS/common/e-ews-folder.c
+src/EWS/common/e-oauth2-service-office365.c
+src/EWS/evolution/e-book-config-ews.c
+src/EWS/evolution/e-ews-config-lookup.c
+src/EWS/evolution/e-ews-config-utils.c
+src/EWS/evolution/e-ews-edit-folder-permissions.c
+src/EWS/evolution/e-ews-ooo-notificator.c
+src/EWS/evolution/e-ews-search-user.c
+src/EWS/evolution/e-ews-subscribe-foreign-folder.c
+src/EWS/evolution/e-mail-config-ews-autodiscover.c
+src/EWS/evolution/e-mail-config-ews-backend.c
+src/EWS/evolution/e-mail-config-ews-delegates-page.c
+src/EWS/evolution/e-mail-config-ews-folder-sizes-page.c
+src/EWS/evolution/e-mail-config-ews-gal.c
+src/EWS/evolution/e-mail-config-ews-ooo-page.c
+src/EWS/evolution/module-ews-configuration.error.xml
+src/EWS/registry/e-ews-backend.c
+src/EWS/registry/module-ews-backend.c
+src/Microsoft365/addressbook/e-book-backend-m365.c
+src/Microsoft365/calendar/e-cal-backend-m365.c
+src/Microsoft365/camel/camel-m365-folder.c
+src/Microsoft365/camel/camel-m365-provider.c
+src/Microsoft365/camel/camel-m365-store.c
+src/Microsoft365/camel/camel-m365-transport.c
+src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.c
+src/Microsoft365/common/e-oauth2-service-microsoft365.c
+src/Microsoft365/common/e-m365-connection.c
+src/Microsoft365/evolution/e-mail-config-m365-backend.c
+src/Microsoft365/registry/e-m365-backend.c
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5b514ee3..32df2977 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,64 +1,2 @@
-macro(add_simple_module _name _sourcesvar _depsvar _defsvar _cflagsvar _incdirsvar _ldflagsvar _destination)
- set(DEPENDENCIES
- evolution-ews
- )
-
- set(SOURCES
- ${${_sourcesvar}}
- )
-
- add_library(${_name} MODULE
- ${SOURCES}
- )
-
- set_target_properties(${_name} PROPERTIES
- PREFIX ""
- )
-
- add_dependencies(${_name}
- ${DEPENDENCIES}
- ${${_depsvar}}
- )
-
- target_compile_definitions(${_name} PRIVATE
- -DG_LOG_DOMAIN=\"${_name}\"
- -DEXCHANGE_EWS_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
- ${${_defsvar}}
- )
-
- target_compile_options(${_name} PUBLIC
- ${LIBEBACKEND_CFLAGS}
- ${LIBEDATASERVER_CFLAGS}
- ${SOUP_CFLAGS}
- ${${_cflagsvar}}
- )
-
- target_include_directories(${_name} PUBLIC
- ${CMAKE_BINARY_DIR}
- ${CMAKE_SOURCE_DIR}
- ${LIBEBACKEND_INCLUDE_DIRS}
- ${LIBEDATASERVER_INCLUDE_DIRS}
- ${SOUP_INCLUDE_DIRS}
- ${${_incdirsvar}}
- )
-
- target_link_libraries(${_name}
- ${DEPENDENCIES}
- ${${_depsvar}}
- ${LIBEBACKEND_LDFLAGS}
- ${LIBEDATASERVER_LDFLAGS}
- ${SOUP_LDFLAGS}
- ${${_ldflagsvar}}
- )
-
- install(TARGETS ${_name}
- DESTINATION ${_destination}
- )
-endmacro(add_simple_module)
-
-add_subdirectory(addressbook)
-add_subdirectory(calendar)
-add_subdirectory(camel)
-add_subdirectory(collection)
-add_subdirectory(configuration)
-add_subdirectory(server)
+add_subdirectory(EWS)
+add_subdirectory(Microsoft365)
diff --git a/src/EWS/CMakeLists.txt b/src/EWS/CMakeLists.txt
new file mode 100644
index 00000000..a2b3b2b1
--- /dev/null
+++ b/src/EWS/CMakeLists.txt
@@ -0,0 +1,64 @@
+macro(add_simple_module _name _sourcesvar _depsvar _defsvar _cflagsvar _incdirsvar _ldflagsvar _destination)
+ set(DEPENDENCIES
+ evolution-ews
+ )
+
+ set(SOURCES
+ ${${_sourcesvar}}
+ )
+
+ add_library(${_name} MODULE
+ ${SOURCES}
+ )
+
+ set_target_properties(${_name} PROPERTIES
+ PREFIX ""
+ )
+
+ add_dependencies(${_name}
+ ${DEPENDENCIES}
+ ${${_depsvar}}
+ )
+
+ target_compile_definitions(${_name} PRIVATE
+ -DG_LOG_DOMAIN=\"${_name}\"
+ -DEXCHANGE_EWS_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ ${${_defsvar}}
+ )
+
+ target_compile_options(${_name} PUBLIC
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBEDATASERVER_CFLAGS}
+ ${SOUP_CFLAGS}
+ ${${_cflagsvar}}
+ )
+
+ target_include_directories(${_name} PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBEDATASERVER_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+ ${${_incdirsvar}}
+ )
+
+ target_link_libraries(${_name}
+ ${DEPENDENCIES}
+ ${${_depsvar}}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBEDATASERVER_LDFLAGS}
+ ${SOUP_LDFLAGS}
+ ${${_ldflagsvar}}
+ )
+
+ install(TARGETS ${_name}
+ DESTINATION ${_destination}
+ )
+endmacro(add_simple_module)
+
+add_subdirectory(addressbook)
+add_subdirectory(calendar)
+add_subdirectory(camel)
+add_subdirectory(common)
+add_subdirectory(evolution)
+add_subdirectory(registry)
diff --git a/src/addressbook/CMakeLists.txt b/src/EWS/addressbook/CMakeLists.txt
similarity index 98%
rename from src/addressbook/CMakeLists.txt
rename to src/EWS/addressbook/CMakeLists.txt
index f2b40098..7b5e5298 100644
--- a/src/addressbook/CMakeLists.txt
+++ b/src/EWS/addressbook/CMakeLists.txt
@@ -53,8 +53,8 @@ target_compile_options(ebookbackendews PUBLIC
target_include_directories(ebookbackendews PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${LIBEBACKEND_INCLUDE_DIRS}
diff --git a/src/addressbook/e-book-backend-ews-factory.c b/src/EWS/addressbook/e-book-backend-ews-factory.c
similarity index 95%
rename from src/addressbook/e-book-backend-ews-factory.c
rename to src/EWS/addressbook/e-book-backend-ews-factory.c
index 28197533..7b0a4d1d 100644
--- a/src/addressbook/e-book-backend-ews-factory.c
+++ b/src/EWS/addressbook/e-book-backend-ews-factory.c
@@ -11,8 +11,8 @@
#include <libedata-book/libedata-book.h>
-#include "server/e-oauth2-service-office365.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-oauth2-service-office365.h"
+#include "common/e-source-ews-folder.h"
#include "e-book-backend-ews.h"
diff --git a/src/addressbook/e-book-backend-ews.c b/src/EWS/addressbook/e-book-backend-ews.c
similarity index 99%
rename from src/addressbook/e-book-backend-ews.c
rename to src/EWS/addressbook/e-book-backend-ews.c
index 893461dc..1f9f2fb0 100644
--- a/src/addressbook/e-book-backend-ews.c
+++ b/src/EWS/addressbook/e-book-backend-ews.c
@@ -27,13 +27,13 @@
#include <libedata-book/libedata-book.h>
-#include "server/e-ews-item-change.h"
-#include "server/e-ews-message.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
-#include "server/e-ews-item.h"
-#include "server/e-ews-query-to-restriction.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-ews-item-change.h"
+#include "common/e-ews-message.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-ews-item.h"
+#include "common/e-ews-query-to-restriction.h"
+#include "common/e-source-ews-folder.h"
#include "e-book-backend-ews.h"
#include "ews-oab-decoder.h"
diff --git a/src/addressbook/e-book-backend-ews.h b/src/EWS/addressbook/e-book-backend-ews.h
similarity index 100%
rename from src/addressbook/e-book-backend-ews.h
rename to src/EWS/addressbook/e-book-backend-ews.h
diff --git a/src/addressbook/ews-oab-decoder.c b/src/EWS/addressbook/ews-oab-decoder.c
similarity index 100%
rename from src/addressbook/ews-oab-decoder.c
rename to src/EWS/addressbook/ews-oab-decoder.c
diff --git a/src/addressbook/ews-oab-decoder.h b/src/EWS/addressbook/ews-oab-decoder.h
similarity index 100%
rename from src/addressbook/ews-oab-decoder.h
rename to src/EWS/addressbook/ews-oab-decoder.h
diff --git a/src/addressbook/ews-oab-decompress.c b/src/EWS/addressbook/ews-oab-decompress.c
similarity index 100%
rename from src/addressbook/ews-oab-decompress.c
rename to src/EWS/addressbook/ews-oab-decompress.c
diff --git a/src/addressbook/ews-oab-decompress.h b/src/EWS/addressbook/ews-oab-decompress.h
similarity index 100%
rename from src/addressbook/ews-oab-decompress.h
rename to src/EWS/addressbook/ews-oab-decompress.h
diff --git a/src/addressbook/ews-oab-props.h b/src/EWS/addressbook/ews-oab-props.h
similarity index 100%
rename from src/addressbook/ews-oab-props.h
rename to src/EWS/addressbook/ews-oab-props.h
diff --git a/src/addressbook/gal-lzx-decompress-test.c b/src/EWS/addressbook/gal-lzx-decompress-test.c
similarity index 100%
rename from src/addressbook/gal-lzx-decompress-test.c
rename to src/EWS/addressbook/gal-lzx-decompress-test.c
diff --git a/src/addressbook/mspack/README.mspack b/src/EWS/addressbook/mspack/README.mspack
similarity index 100%
rename from src/addressbook/mspack/README.mspack
rename to src/EWS/addressbook/mspack/README.mspack
diff --git a/src/addressbook/mspack/lzx.h b/src/EWS/addressbook/mspack/lzx.h
similarity index 100%
rename from src/addressbook/mspack/lzx.h
rename to src/EWS/addressbook/mspack/lzx.h
diff --git a/src/addressbook/mspack/lzxd.c b/src/EWS/addressbook/mspack/lzxd.c
similarity index 100%
rename from src/addressbook/mspack/lzxd.c
rename to src/EWS/addressbook/mspack/lzxd.c
diff --git a/src/addressbook/mspack/oab-decompress.c b/src/EWS/addressbook/mspack/oab-decompress.c
similarity index 100%
rename from src/addressbook/mspack/oab-decompress.c
rename to src/EWS/addressbook/mspack/oab-decompress.c
diff --git a/src/addressbook/mspack/readbits.h b/src/EWS/addressbook/mspack/readbits.h
similarity index 100%
rename from src/addressbook/mspack/readbits.h
rename to src/EWS/addressbook/mspack/readbits.h
diff --git a/src/addressbook/mspack/readhuff.h b/src/EWS/addressbook/mspack/readhuff.h
similarity index 100%
rename from src/addressbook/mspack/readhuff.h
rename to src/EWS/addressbook/mspack/readhuff.h
diff --git a/src/addressbook/oab-decode-test.c b/src/EWS/addressbook/oab-decode-test.c
similarity index 100%
rename from src/addressbook/oab-decode-test.c
rename to src/EWS/addressbook/oab-decode-test.c
diff --git a/src/calendar/CMakeLists.txt b/src/EWS/calendar/CMakeLists.txt
similarity index 95%
rename from src/calendar/CMakeLists.txt
rename to src/EWS/calendar/CMakeLists.txt
index 4492b211..3a6ce029 100644
--- a/src/calendar/CMakeLists.txt
+++ b/src/EWS/calendar/CMakeLists.txt
@@ -41,8 +41,8 @@ target_compile_options(ecalbackendews PUBLIC
target_include_directories(ecalbackendews PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${CMAKE_CURRENT_BINARY_DIR}
${CAMEL_INCLUDE_DIRS}
${EVOLUTION_CALENDAR_INCLUDE_DIRS}
diff --git a/src/calendar/e-cal-backend-ews-factory.c b/src/EWS/calendar/e-cal-backend-ews-factory.c
similarity index 97%
rename from src/calendar/e-cal-backend-ews-factory.c
rename to src/EWS/calendar/e-cal-backend-ews-factory.c
index bd489806..820eaf1d 100644
--- a/src/calendar/e-cal-backend-ews-factory.c
+++ b/src/EWS/calendar/e-cal-backend-ews-factory.c
@@ -11,8 +11,8 @@
#include <libedata-cal/libedata-cal.h>
-#include "server/e-oauth2-service-office365.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-oauth2-service-office365.h"
+#include "common/e-source-ews-folder.h"
#include "e-cal-backend-ews.h"
diff --git a/src/calendar/e-cal-backend-ews-utils.c b/src/EWS/calendar/e-cal-backend-ews-utils.c
similarity index 99%
rename from src/calendar/e-cal-backend-ews-utils.c
rename to src/EWS/calendar/e-cal-backend-ews-utils.c
index 017f2caf..10cc4f11 100644
--- a/src/calendar/e-cal-backend-ews-utils.c
+++ b/src/EWS/calendar/e-cal-backend-ews-utils.c
@@ -29,10 +29,10 @@
#include <libecal/libecal.h>
#include <libsoup/soup-misc.h>
-#include "server/e-ews-calendar-utils.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-message.h"
-#include "server/e-ews-item-change.h"
+#include "common/e-ews-calendar-utils.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-message.h"
+#include "common/e-ews-item-change.h"
#include "e-cal-backend-ews-utils.h"
diff --git a/src/calendar/e-cal-backend-ews-utils.h b/src/EWS/calendar/e-cal-backend-ews-utils.h
similarity index 97%
rename from src/calendar/e-cal-backend-ews-utils.h
rename to src/EWS/calendar/e-cal-backend-ews-utils.h
index c491f258..2f0da704 100644
--- a/src/calendar/e-cal-backend-ews-utils.h
+++ b/src/EWS/calendar/e-cal-backend-ews-utils.h
@@ -9,8 +9,8 @@
#include <libecal/libecal.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-item-change.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-item-change.h"
#include "e-cal-backend-ews.h"
diff --git a/src/calendar/e-cal-backend-ews.c b/src/EWS/calendar/e-cal-backend-ews.c
similarity index 99%
rename from src/calendar/e-cal-backend-ews.c
rename to src/EWS/calendar/e-cal-backend-ews.c
index 2de76fb0..dd7fb647 100644
--- a/src/calendar/e-cal-backend-ews.c
+++ b/src/EWS/calendar/e-cal-backend-ews.c
@@ -24,10 +24,10 @@
#include <calendar/gui/calendar-config.h>
#include <calendar/gui/itip-utils.h>
-#include "server/e-source-ews-folder.h"
-#include "server/e-ews-calendar-utils.h"
-#include "server/e-ews-connection-utils.h"
-#include "server/e-ews-camel-common.h"
+#include "common/e-source-ews-folder.h"
+#include "common/e-ews-calendar-utils.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-ews-camel-common.h"
#include "e-cal-backend-ews.h"
#include "e-cal-backend-ews-utils.h"
diff --git a/src/calendar/e-cal-backend-ews.h b/src/EWS/calendar/e-cal-backend-ews.h
similarity index 97%
rename from src/calendar/e-cal-backend-ews.h
rename to src/EWS/calendar/e-cal-backend-ews.h
index 498b179f..f7a42296 100644
--- a/src/calendar/e-cal-backend-ews.h
+++ b/src/EWS/calendar/e-cal-backend-ews.h
@@ -9,7 +9,7 @@
#include <libedata-cal/libedata-cal.h>
-#include "server/e-ews-connection.h"
+#include "common/e-ews-connection.h"
G_BEGIN_DECLS
diff --git a/src/calendar/windowsZones.xml b/src/EWS/calendar/windowsZones.xml
similarity index 100%
rename from src/calendar/windowsZones.xml
rename to src/EWS/calendar/windowsZones.xml
diff --git a/src/camel/CMakeLists.txt b/src/EWS/camel/CMakeLists.txt
similarity index 96%
rename from src/camel/CMakeLists.txt
rename to src/EWS/camel/CMakeLists.txt
index 469eb0f9..bf80fd4b 100644
--- a/src/camel/CMakeLists.txt
+++ b/src/EWS/camel/CMakeLists.txt
@@ -60,8 +60,8 @@ target_compile_options(camelews-priv PUBLIC
target_include_directories(camelews-priv PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${CMAKE_CURRENT_BINARY_DIR}
${CAMEL_INCLUDE_DIRS}
${EVOLUTION_SHELL_INCLUDE_DIRS}
@@ -123,8 +123,8 @@ target_compile_options(camelews PUBLIC
target_include_directories(camelews PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${CMAKE_CURRENT_BINARY_DIR}
${CAMEL_INCLUDE_DIRS}
${EVOLUTION_SHELL_INCLUDE_DIRS}
diff --git a/src/camel/camel-ews-enums.h b/src/EWS/camel/camel-ews-enums.h
similarity index 100%
rename from src/camel/camel-ews-enums.h
rename to src/EWS/camel/camel-ews-enums.h
diff --git a/src/camel/camel-ews-folder.c b/src/EWS/camel/camel-ews-folder.c
similarity index 99%
rename from src/camel/camel-ews-folder.c
rename to src/EWS/camel/camel-ews-folder.c
index 1c829825..5d6b77fb 100644
--- a/src/camel/camel-ews-folder.c
+++ b/src/EWS/camel/camel-ews-folder.c
@@ -23,11 +23,11 @@ which needs to be better organized via functions */
#include <glib/gstdio.h>
#include <libecal/libecal.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-camel-common.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-item-change.h"
-#include "server/e-ews-message.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-camel-common.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-item-change.h"
+#include "common/e-ews-message.h"
#include "camel-ews-folder.h"
#include "camel-ews-private.h"
diff --git a/src/camel/camel-ews-folder.h b/src/EWS/camel/camel-ews-folder.h
similarity index 100%
rename from src/camel/camel-ews-folder.h
rename to src/EWS/camel/camel-ews-folder.h
diff --git a/src/camel/camel-ews-message-info.c b/src/EWS/camel/camel-ews-message-info.c
similarity index 100%
rename from src/camel/camel-ews-message-info.c
rename to src/EWS/camel/camel-ews-message-info.c
diff --git a/src/camel/camel-ews-message-info.h b/src/EWS/camel/camel-ews-message-info.h
similarity index 100%
rename from src/camel/camel-ews-message-info.h
rename to src/EWS/camel/camel-ews-message-info.h
diff --git a/src/camel/camel-ews-private.h b/src/EWS/camel/camel-ews-private.h
similarity index 100%
rename from src/camel/camel-ews-private.h
rename to src/EWS/camel/camel-ews-private.h
diff --git a/src/camel/camel-ews-provider.c b/src/EWS/camel/camel-ews-provider.c
similarity index 99%
rename from src/camel/camel-ews-provider.c
rename to src/EWS/camel/camel-ews-provider.c
index fae6a5b2..1c389dbd 100644
--- a/src/camel/camel-ews-provider.c
+++ b/src/EWS/camel/camel-ews-provider.c
@@ -14,7 +14,7 @@
#include <glib/gi18n-lib.h>
#include <gmodule.h>
-#include "server/camel-sasl-xoauth2-office365.h"
+#include "common/camel-sasl-xoauth2-office365.h"
#include "camel-ews-store.h"
#include "camel-ews-transport.h"
diff --git a/src/camel/camel-ews-search.c b/src/EWS/camel/camel-ews-search.c
similarity index 99%
rename from src/camel/camel-ews-search.c
rename to src/EWS/camel/camel-ews-search.c
index 16da4cd3..dae80f77 100644
--- a/src/camel/camel-ews-search.c
+++ b/src/EWS/camel/camel-ews-search.c
@@ -11,7 +11,7 @@
#include <camel/camel-search-private.h>
#include <e-util/e-util.h>
-#include "server/e-ews-query-to-restriction.h"
+#include "common/e-ews-query-to-restriction.h"
#include "camel-ews-folder.h"
#include "camel-ews-search.h"
diff --git a/src/camel/camel-ews-search.h b/src/EWS/camel/camel-ews-search.h
similarity index 100%
rename from src/camel/camel-ews-search.h
rename to src/EWS/camel/camel-ews-search.h
diff --git a/src/camel/camel-ews-store-summary.c b/src/EWS/camel/camel-ews-store-summary.c
similarity index 99%
rename from src/camel/camel-ews-store-summary.c
rename to src/EWS/camel/camel-ews-store-summary.c
index 776abf0b..dd4cc55c 100644
--- a/src/camel/camel-ews-store-summary.c
+++ b/src/EWS/camel/camel-ews-store-summary.c
@@ -12,7 +12,7 @@
#include <string.h>
#include "camel-ews-store-summary.h"
-#include "server/e-ews-folder.h"
+#include "common/e-ews-folder.h"
#define S_LOCK(x) (g_rec_mutex_lock(&(x)->priv->s_lock))
#define S_UNLOCK(x) (g_rec_mutex_unlock(&(x)->priv->s_lock))
diff --git a/src/camel/camel-ews-store-summary.h b/src/EWS/camel/camel-ews-store-summary.h
similarity index 99%
rename from src/camel/camel-ews-store-summary.h
rename to src/EWS/camel/camel-ews-store-summary.h
index 15c023be..16ab0117 100644
--- a/src/camel/camel-ews-store-summary.h
+++ b/src/EWS/camel/camel-ews-store-summary.h
@@ -9,7 +9,7 @@
#include <camel/camel.h>
-#include "server/e-ews-enums.h"
+#include "common/e-ews-enums.h"
/* Standard GObject macros */
#define CAMEL_TYPE_EWS_STORE_SUMMARY \
diff --git a/src/camel/camel-ews-store.c b/src/EWS/camel/camel-ews-store.c
similarity index 99%
rename from src/camel/camel-ews-store.c
rename to src/EWS/camel/camel-ews-store.c
index 744f8e58..b46e8b99 100644
--- a/src/camel/camel-ews-store.c
+++ b/src/EWS/camel/camel-ews-store.c
@@ -20,10 +20,10 @@
#include <libemail-engine/libemail-engine.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-item-change.h"
-#include "server/e-ews-message.h"
-#include "server/e-ews-oof-settings.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-item-change.h"
+#include "common/e-ews-message.h"
+#include "common/e-ews-oof-settings.h"
#include "camel-ews-folder.h"
#include "camel-ews-store.h"
diff --git a/src/camel/camel-ews-store.h b/src/EWS/camel/camel-ews-store.h
similarity index 98%
rename from src/camel/camel-ews-store.h
rename to src/EWS/camel/camel-ews-store.h
index e210d3d1..614c8ac2 100644
--- a/src/camel/camel-ews-store.h
+++ b/src/EWS/camel/camel-ews-store.h
@@ -11,7 +11,7 @@
#include <camel/camel.h>
#include <camel/camel-ews-enums.h>
-#include "server/e-ews-connection.h"
+#include "common/e-ews-connection.h"
#include "camel-ews-store-summary.h"
diff --git a/src/camel/camel-ews-summary.c b/src/EWS/camel/camel-ews-summary.c
similarity index 100%
rename from src/camel/camel-ews-summary.c
rename to src/EWS/camel/camel-ews-summary.c
diff --git a/src/camel/camel-ews-summary.h b/src/EWS/camel/camel-ews-summary.h
similarity index 100%
rename from src/camel/camel-ews-summary.h
rename to src/EWS/camel/camel-ews-summary.h
diff --git a/src/camel/camel-ews-transport.c b/src/EWS/camel/camel-ews-transport.c
similarity index 99%
rename from src/camel/camel-ews-transport.c
rename to src/EWS/camel/camel-ews-transport.c
index fbf49618..54f2984d 100644
--- a/src/camel/camel-ews-transport.c
+++ b/src/EWS/camel/camel-ews-transport.c
@@ -14,8 +14,8 @@
#include <libemail-engine/libemail-engine.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-camel-common.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-camel-common.h"
#include "camel-ews-store.h"
#include "camel-ews-transport.h"
diff --git a/src/camel/camel-ews-transport.h b/src/EWS/camel/camel-ews-transport.h
similarity index 100%
rename from src/camel/camel-ews-transport.h
rename to src/EWS/camel/camel-ews-transport.h
diff --git a/src/camel/camel-ews-utils.c b/src/EWS/camel/camel-ews-utils.c
similarity index 99%
rename from src/camel/camel-ews-utils.c
rename to src/EWS/camel/camel-ews-utils.c
index 02467c31..7c08e8f8 100644
--- a/src/camel/camel-ews-utils.c
+++ b/src/EWS/camel/camel-ews-utils.c
@@ -17,10 +17,10 @@
#include <libemail-engine/libemail-engine.h>
#include <e-util/e-util.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-camel-common.h"
-#include "server/e-ews-item-change.h"
-#include "server/e-ews-message.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-camel-common.h"
+#include "common/e-ews-item-change.h"
+#include "common/e-ews-message.h"
#include "camel-ews-utils.h"
diff --git a/src/camel/camel-ews-utils.h b/src/EWS/camel/camel-ews-utils.h
similarity index 98%
rename from src/camel/camel-ews-utils.h
rename to src/EWS/camel/camel-ews-utils.h
index 15986587..db0e6a17 100644
--- a/src/camel/camel-ews-utils.h
+++ b/src/EWS/camel/camel-ews-utils.h
@@ -9,7 +9,7 @@
#include <camel/camel.h>
-#include "server/e-ews-connection.h"
+#include "common/e-ews-connection.h"
#include "camel-ews-store.h"
#include "camel-ews-folder.h"
diff --git a/src/camel/libcamelews.urls b/src/EWS/camel/libcamelews.urls
similarity index 100%
rename from src/camel/libcamelews.urls
rename to src/EWS/camel/libcamelews.urls
diff --git a/src/server/CMakeLists.txt b/src/EWS/common/CMakeLists.txt
similarity index 97%
rename from src/server/CMakeLists.txt
rename to src/EWS/common/CMakeLists.txt
index 94dcb0b4..036d8e5a 100644
--- a/src/server/CMakeLists.txt
+++ b/src/EWS/common/CMakeLists.txt
@@ -65,8 +65,8 @@ target_compile_options(evolution-ews PUBLIC
target_include_directories(evolution-ews PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${CMAKE_CURRENT_BINARY_DIR}
${CAMEL_INCLUDE_DIRS}
${EVOLUTION_CALENDAR_INCLUDE_DIRS}
diff --git a/src/server/camel-ews-settings.c b/src/EWS/common/camel-ews-settings.c
similarity index 100%
rename from src/server/camel-ews-settings.c
rename to src/EWS/common/camel-ews-settings.c
diff --git a/src/server/camel-ews-settings.h b/src/EWS/common/camel-ews-settings.h
similarity index 100%
rename from src/server/camel-ews-settings.h
rename to src/EWS/common/camel-ews-settings.h
diff --git a/src/server/camel-sasl-xoauth2-office365.c b/src/EWS/common/camel-sasl-xoauth2-office365.c
similarity index 100%
rename from src/server/camel-sasl-xoauth2-office365.c
rename to src/EWS/common/camel-sasl-xoauth2-office365.c
diff --git a/src/server/camel-sasl-xoauth2-office365.h b/src/EWS/common/camel-sasl-xoauth2-office365.h
similarity index 100%
rename from src/server/camel-sasl-xoauth2-office365.h
rename to src/EWS/common/camel-sasl-xoauth2-office365.h
diff --git a/src/server/e-ews-calendar-utils.c b/src/EWS/common/e-ews-calendar-utils.c
similarity index 100%
rename from src/server/e-ews-calendar-utils.c
rename to src/EWS/common/e-ews-calendar-utils.c
diff --git a/src/server/e-ews-calendar-utils.h b/src/EWS/common/e-ews-calendar-utils.h
similarity index 94%
rename from src/server/e-ews-calendar-utils.h
rename to src/EWS/common/e-ews-calendar-utils.h
index 5f1f4672..4b640df9 100644
--- a/src/server/e-ews-calendar-utils.h
+++ b/src/EWS/common/e-ews-calendar-utils.h
@@ -10,8 +10,8 @@
#include <time.h>
#include <libecal/libecal.h>
-#include "server/e-soap-message.h"
-#include "server/e-ews-item.h"
+#include "common/e-soap-message.h"
+#include "common/e-ews-item.h"
G_BEGIN_DECLS
diff --git a/src/server/e-ews-camel-common.c b/src/EWS/common/e-ews-camel-common.c
similarity index 99%
rename from src/server/e-ews-camel-common.c
rename to src/EWS/common/e-ews-camel-common.c
index 9e22ca14..3bd5a356 100644
--- a/src/server/e-ews-camel-common.c
+++ b/src/EWS/common/e-ews-camel-common.c
@@ -9,8 +9,8 @@
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
-#include "server/e-ews-message.h"
-#include "server/e-ews-item-change.h"
+#include "common/e-ews-message.h"
+#include "common/e-ews-item-change.h"
#include "e-ews-camel-common.h"
diff --git a/src/server/e-ews-camel-common.h b/src/EWS/common/e-ews-camel-common.h
similarity index 91%
rename from src/server/e-ews-camel-common.h
rename to src/EWS/common/e-ews-camel-common.h
index 873084c8..b1c6add0 100644
--- a/src/server/e-ews-camel-common.h
+++ b/src/EWS/common/e-ews-camel-common.h
@@ -9,8 +9,8 @@
#include <camel/camel.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-folder.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-folder.h"
G_BEGIN_DECLS
diff --git a/src/server/e-ews-connection-utils.c b/src/EWS/common/e-ews-connection-utils.c
similarity index 100%
rename from src/server/e-ews-connection-utils.c
rename to src/EWS/common/e-ews-connection-utils.c
diff --git a/src/server/e-ews-connection-utils.h b/src/EWS/common/e-ews-connection-utils.h
similarity index 97%
rename from src/server/e-ews-connection-utils.h
rename to src/EWS/common/e-ews-connection-utils.h
index fc612d70..95deb9f2 100644
--- a/src/server/e-ews-connection-utils.h
+++ b/src/EWS/common/e-ews-connection-utils.h
@@ -8,7 +8,7 @@
#define E_EWS_CONNECTION_UTILS_H
#include <glib.h>
-#include <server/e-ews-connection.h>
+#include <common/e-ews-connection.h>
G_BEGIN_DECLS
diff --git a/src/server/e-ews-connection.c b/src/EWS/common/e-ews-connection.c
similarity index 100%
rename from src/server/e-ews-connection.c
rename to src/EWS/common/e-ews-connection.c
diff --git a/src/server/e-ews-connection.h b/src/EWS/common/e-ews-connection.h
similarity index 100%
rename from src/server/e-ews-connection.h
rename to src/EWS/common/e-ews-connection.h
diff --git a/src/server/e-ews-debug.c b/src/EWS/common/e-ews-debug.c
similarity index 100%
rename from src/server/e-ews-debug.c
rename to src/EWS/common/e-ews-debug.c
diff --git a/src/server/e-ews-debug.h b/src/EWS/common/e-ews-debug.h
similarity index 94%
rename from src/server/e-ews-debug.h
rename to src/EWS/common/e-ews-debug.h
index 0f648511..46daf228 100644
--- a/src/server/e-ews-debug.h
+++ b/src/EWS/common/e-ews-debug.h
@@ -8,7 +8,7 @@
#define E_EWS_DEBUG_H
#include <glib.h>
-#include <server/e-ews-connection.h>
+#include <common/e-ews-connection.h>
G_BEGIN_DECLS
diff --git a/src/server/e-ews-enums.h b/src/EWS/common/e-ews-enums.h
similarity index 100%
rename from src/server/e-ews-enums.h
rename to src/EWS/common/e-ews-enums.h
diff --git a/src/server/e-ews-folder.c b/src/EWS/common/e-ews-folder.c
similarity index 100%
rename from src/server/e-ews-folder.c
rename to src/EWS/common/e-ews-folder.c
diff --git a/src/server/e-ews-folder.h b/src/EWS/common/e-ews-folder.h
similarity index 100%
rename from src/server/e-ews-folder.h
rename to src/EWS/common/e-ews-folder.h
diff --git a/src/server/e-ews-item-change.c b/src/EWS/common/e-ews-item-change.c
similarity index 100%
rename from src/server/e-ews-item-change.c
rename to src/EWS/common/e-ews-item-change.c
diff --git a/src/server/e-ews-item-change.h b/src/EWS/common/e-ews-item-change.h
similarity index 100%
rename from src/server/e-ews-item-change.h
rename to src/EWS/common/e-ews-item-change.h
diff --git a/src/server/e-ews-item.c b/src/EWS/common/e-ews-item.c
similarity index 100%
rename from src/server/e-ews-item.c
rename to src/EWS/common/e-ews-item.c
diff --git a/src/server/e-ews-item.h b/src/EWS/common/e-ews-item.h
similarity index 100%
rename from src/server/e-ews-item.h
rename to src/EWS/common/e-ews-item.h
diff --git a/src/server/e-ews-message.c b/src/EWS/common/e-ews-message.c
similarity index 100%
rename from src/server/e-ews-message.c
rename to src/EWS/common/e-ews-message.c
diff --git a/src/server/e-ews-message.h b/src/EWS/common/e-ews-message.h
similarity index 100%
rename from src/server/e-ews-message.h
rename to src/EWS/common/e-ews-message.h
diff --git a/src/server/e-ews-notification.c b/src/EWS/common/e-ews-notification.c
similarity index 100%
rename from src/server/e-ews-notification.c
rename to src/EWS/common/e-ews-notification.c
diff --git a/src/server/e-ews-notification.h b/src/EWS/common/e-ews-notification.h
similarity index 100%
rename from src/server/e-ews-notification.h
rename to src/EWS/common/e-ews-notification.h
diff --git a/src/server/e-ews-oof-settings.c b/src/EWS/common/e-ews-oof-settings.c
similarity index 100%
rename from src/server/e-ews-oof-settings.c
rename to src/EWS/common/e-ews-oof-settings.c
diff --git a/src/server/e-ews-oof-settings.h b/src/EWS/common/e-ews-oof-settings.h
similarity index 98%
rename from src/server/e-ews-oof-settings.h
rename to src/EWS/common/e-ews-oof-settings.h
index e8b87bd3..a6c2e1c8 100644
--- a/src/server/e-ews-oof-settings.h
+++ b/src/EWS/common/e-ews-oof-settings.h
@@ -7,8 +7,8 @@
#ifndef E_EWS_OOF_SETTINGS_H
#define E_EWS_OOF_SETTINGS_H
-#include <server/e-ews-connection.h>
-#include <server/e-ews-enums.h>
+#include <common/e-ews-connection.h>
+#include <common/e-ews-enums.h>
/* Standard GObject macros */
#define E_TYPE_EWS_OOF_SETTINGS \
diff --git a/src/server/e-ews-query-to-restriction.c b/src/EWS/common/e-ews-query-to-restriction.c
similarity index 99%
rename from src/server/e-ews-query-to-restriction.c
rename to src/EWS/common/e-ews-query-to-restriction.c
index ab879424..cedbf9c1 100644
--- a/src/server/e-ews-query-to-restriction.c
+++ b/src/EWS/common/e-ews-query-to-restriction.c
@@ -13,7 +13,7 @@
#include <camel/camel.h>
#include <libedata-cal/libedata-cal.h>
-#include "server/e-ews-message.h"
+#include "e-ews-message.h"
#include "e-ews-query-to-restriction.h"
diff --git a/src/server/e-ews-query-to-restriction.h b/src/EWS/common/e-ews-query-to-restriction.h
similarity index 89%
rename from src/server/e-ews-query-to-restriction.h
rename to src/EWS/common/e-ews-query-to-restriction.h
index 486b20e5..6cc00cec 100644
--- a/src/server/e-ews-query-to-restriction.h
+++ b/src/EWS/common/e-ews-query-to-restriction.h
@@ -8,8 +8,8 @@
#ifndef E_EWS_QUERY_TO_RESTRICTION_H
#define E_EWS_QUERY_TO_RESTRICTION_H
-#include "server/e-soap-message.h"
-#include "server/e-ews-folder.h"
+#include "common/e-soap-message.h"
+#include "common/e-ews-folder.h"
gboolean e_ews_query_check_applicable (const gchar *query,
EEwsFolderType type);
diff --git a/src/server/e-oauth2-service-office365.c b/src/EWS/common/e-oauth2-service-office365.c
similarity index 99%
rename from src/server/e-oauth2-service-office365.c
rename to src/EWS/common/e-oauth2-service-office365.c
index 8f3a3d8f..1e50e680 100644
--- a/src/server/e-oauth2-service-office365.c
+++ b/src/EWS/common/e-oauth2-service-office365.c
@@ -9,7 +9,7 @@
#include <glib/gi18n-lib.h>
#include <libedataserver/libedataserver.h>
-#include "server/camel-ews-settings.h"
+#include "camel-ews-settings.h"
#include "e-oauth2-service-office365.h"
diff --git a/src/server/e-oauth2-service-office365.h b/src/EWS/common/e-oauth2-service-office365.h
similarity index 100%
rename from src/server/e-oauth2-service-office365.h
rename to src/EWS/common/e-oauth2-service-office365.h
diff --git a/src/server/e-soap-message.c b/src/EWS/common/e-soap-message.c
similarity index 100%
rename from src/server/e-soap-message.c
rename to src/EWS/common/e-soap-message.c
diff --git a/src/server/e-soap-message.h b/src/EWS/common/e-soap-message.h
similarity index 100%
rename from src/server/e-soap-message.h
rename to src/EWS/common/e-soap-message.h
diff --git a/src/server/e-soap-response.c b/src/EWS/common/e-soap-response.c
similarity index 100%
rename from src/server/e-soap-response.c
rename to src/EWS/common/e-soap-response.c
diff --git a/src/server/e-soap-response.h b/src/EWS/common/e-soap-response.h
similarity index 100%
rename from src/server/e-soap-response.h
rename to src/EWS/common/e-soap-response.h
diff --git a/src/server/e-soup-auth-negotiate.c b/src/EWS/common/e-soup-auth-negotiate.c
similarity index 100%
rename from src/server/e-soup-auth-negotiate.c
rename to src/EWS/common/e-soup-auth-negotiate.c
diff --git a/src/server/e-soup-auth-negotiate.h b/src/EWS/common/e-soup-auth-negotiate.h
similarity index 100%
rename from src/server/e-soup-auth-negotiate.h
rename to src/EWS/common/e-soup-auth-negotiate.h
diff --git a/src/server/e-source-ews-folder.c b/src/EWS/common/e-source-ews-folder.c
similarity index 100%
rename from src/server/e-source-ews-folder.c
rename to src/EWS/common/e-source-ews-folder.c
diff --git a/src/server/e-source-ews-folder.h b/src/EWS/common/e-source-ews-folder.h
similarity index 99%
rename from src/server/e-source-ews-folder.h
rename to src/EWS/common/e-source-ews-folder.h
index 596c20ca..984b5e57 100644
--- a/src/server/e-source-ews-folder.h
+++ b/src/EWS/common/e-source-ews-folder.h
@@ -8,7 +8,7 @@
#define E_SOURCE_EWS_FOLDER_H
#include <libedataserver/libedataserver.h>
-#include "server/e-ews-folder.h"
+#include "common/e-ews-folder.h"
/* Standard GObject macros */
#define E_TYPE_SOURCE_EWS_FOLDER \
diff --git a/src/server/ews-errors.c b/src/EWS/common/ews-errors.c
similarity index 100%
rename from src/server/ews-errors.c
rename to src/EWS/common/ews-errors.c
diff --git a/src/server/ews-errors.h b/src/EWS/common/ews-errors.h
similarity index 99%
rename from src/server/ews-errors.h
rename to src/EWS/common/ews-errors.h
index a7350702..262903b2 100644
--- a/src/server/ews-errors.h
+++ b/src/EWS/common/ews-errors.h
@@ -7,7 +7,7 @@
#ifndef EWS_ERRORS_H
#define EWS_ERRORS_H
-#include <server/e-soap-response.h>
+#include <common/e-soap-response.h>
G_BEGIN_DECLS
diff --git a/src/configuration/CMakeLists.txt b/src/EWS/evolution/CMakeLists.txt
similarity index 100%
rename from src/configuration/CMakeLists.txt
rename to src/EWS/evolution/CMakeLists.txt
diff --git a/src/configuration/e-book-config-ews.c b/src/EWS/evolution/e-book-config-ews.c
similarity index 98%
rename from src/configuration/e-book-config-ews.c
rename to src/EWS/evolution/e-book-config-ews.c
index d9e40cea..6a4eec19 100644
--- a/src/configuration/e-book-config-ews.c
+++ b/src/EWS/evolution/e-book-config-ews.c
@@ -8,8 +8,8 @@
#include <glib/gi18n-lib.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-source-ews-folder.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-source-ews-folder.h"
#include "e-book-config-ews.h"
diff --git a/src/configuration/e-book-config-ews.h b/src/EWS/evolution/e-book-config-ews.h
similarity index 100%
rename from src/configuration/e-book-config-ews.h
rename to src/EWS/evolution/e-book-config-ews.h
diff --git a/src/configuration/e-cal-config-ews.c b/src/EWS/evolution/e-cal-config-ews.c
similarity index 100%
rename from src/configuration/e-cal-config-ews.c
rename to src/EWS/evolution/e-cal-config-ews.c
diff --git a/src/configuration/e-cal-config-ews.h b/src/EWS/evolution/e-cal-config-ews.h
similarity index 100%
rename from src/configuration/e-cal-config-ews.h
rename to src/EWS/evolution/e-cal-config-ews.h
diff --git a/src/configuration/e-ews-config-lookup.c b/src/EWS/evolution/e-ews-config-lookup.c
similarity index 99%
rename from src/configuration/e-ews-config-lookup.c
rename to src/EWS/evolution/e-ews-config-lookup.c
index b818ccc2..947c4c14 100644
--- a/src/configuration/e-ews-config-lookup.c
+++ b/src/EWS/evolution/e-ews-config-lookup.c
@@ -10,8 +10,8 @@
#include <e-util/e-util.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-connection.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-connection.h"
#include "e-ews-config-lookup.h"
#define E_TYPE_EWS_CONFIG_LOOKUP_RESULT \
diff --git a/src/configuration/e-ews-config-lookup.h b/src/EWS/evolution/e-ews-config-lookup.h
similarity index 100%
rename from src/configuration/e-ews-config-lookup.h
rename to src/EWS/evolution/e-ews-config-lookup.h
diff --git a/src/configuration/e-ews-config-ui-extension.c b/src/EWS/evolution/e-ews-config-ui-extension.c
similarity index 100%
rename from src/configuration/e-ews-config-ui-extension.c
rename to src/EWS/evolution/e-ews-config-ui-extension.c
diff --git a/src/configuration/e-ews-config-ui-extension.h b/src/EWS/evolution/e-ews-config-ui-extension.h
similarity index 100%
rename from src/configuration/e-ews-config-ui-extension.h
rename to src/EWS/evolution/e-ews-config-ui-extension.h
diff --git a/src/configuration/e-ews-config-utils.c b/src/EWS/evolution/e-ews-config-utils.c
similarity index 99%
rename from src/configuration/e-ews-config-utils.c
rename to src/EWS/evolution/e-ews-config-utils.c
index 34a93227..68baab67 100644
--- a/src/configuration/e-ews-config-utils.c
+++ b/src/EWS/evolution/e-ews-config-utils.c
@@ -23,9 +23,9 @@
#include <shell/e-shell-view.h>
#include <shell/e-shell-window.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-source-ews-folder.h"
#include "e-ews-edit-folder-permissions.h"
diff --git a/src/configuration/e-ews-config-utils.h b/src/EWS/evolution/e-ews-config-utils.h
similarity index 98%
rename from src/configuration/e-ews-config-utils.h
rename to src/EWS/evolution/e-ews-config-utils.h
index ce4c8c35..14db9377 100644
--- a/src/configuration/e-ews-config-utils.h
+++ b/src/EWS/evolution/e-ews-config-utils.h
@@ -11,7 +11,7 @@
#include <shell/e-shell-view.h>
-#include "server/e-ews-connection.h"
+#include "common/e-ews-connection.h"
#include "camel/camel-ews-store.h"
typedef void (* EEwsSetupFunc) (GObject *with_object,
diff --git a/src/configuration/e-ews-edit-folder-permissions.c
b/src/EWS/evolution/e-ews-edit-folder-permissions.c
similarity index 100%
rename from src/configuration/e-ews-edit-folder-permissions.c
rename to src/EWS/evolution/e-ews-edit-folder-permissions.c
diff --git a/src/configuration/e-ews-edit-folder-permissions.h
b/src/EWS/evolution/e-ews-edit-folder-permissions.h
similarity index 86%
rename from src/configuration/e-ews-edit-folder-permissions.h
rename to src/EWS/evolution/e-ews-edit-folder-permissions.h
index ba559c9a..55429dc1 100644
--- a/src/configuration/e-ews-edit-folder-permissions.h
+++ b/src/EWS/evolution/e-ews-edit-folder-permissions.h
@@ -10,9 +10,9 @@
#include <gtk/gtk.h>
#include <libedataserver/libedataserver.h>
-#include "server/e-ews-item.h"
-#include "server/e-ews-folder.h"
-#include "server/camel-ews-settings.h"
+#include "common/e-ews-item.h"
+#include "common/e-ews-folder.h"
+#include "common/camel-ews-settings.h"
void e_ews_edit_folder_permissions (GtkWindow *parent,
ESourceRegistry *registry,
diff --git a/src/configuration/e-ews-ooo-notificator.c b/src/EWS/evolution/e-ews-ooo-notificator.c
similarity index 99%
rename from src/configuration/e-ews-ooo-notificator.c
rename to src/EWS/evolution/e-ews-ooo-notificator.c
index 2fcb34a3..19a96bda 100644
--- a/src/configuration/e-ews-ooo-notificator.c
+++ b/src/EWS/evolution/e-ews-ooo-notificator.c
@@ -8,7 +8,7 @@
#include "e-ews-ooo-notificator.h"
#include "camel/camel-ews-store.h"
-#include "server/e-ews-oof-settings.h"
+#include "common/e-ews-oof-settings.h"
#include <shell/e-shell.h>
#include <shell/e-shell-view.h>
diff --git a/src/configuration/e-ews-ooo-notificator.h b/src/EWS/evolution/e-ews-ooo-notificator.h
similarity index 100%
rename from src/configuration/e-ews-ooo-notificator.h
rename to src/EWS/evolution/e-ews-ooo-notificator.h
diff --git a/src/configuration/e-ews-photo-source.c b/src/EWS/evolution/e-ews-photo-source.c
similarity index 98%
rename from src/configuration/e-ews-photo-source.c
rename to src/EWS/evolution/e-ews-photo-source.c
index edf5f391..732d6390 100644
--- a/src/configuration/e-ews-photo-source.c
+++ b/src/EWS/evolution/e-ews-photo-source.c
@@ -8,8 +8,8 @@
#include <e-util/e-util.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-connection.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-connection.h"
#include "e-ews-photo-source.h"
diff --git a/src/configuration/e-ews-photo-source.h b/src/EWS/evolution/e-ews-photo-source.h
similarity index 100%
rename from src/configuration/e-ews-photo-source.h
rename to src/EWS/evolution/e-ews-photo-source.h
diff --git a/src/configuration/e-ews-search-user.c b/src/EWS/evolution/e-ews-search-user.c
similarity index 100%
rename from src/configuration/e-ews-search-user.c
rename to src/EWS/evolution/e-ews-search-user.c
diff --git a/src/configuration/e-ews-search-user.h b/src/EWS/evolution/e-ews-search-user.h
similarity index 87%
rename from src/configuration/e-ews-search-user.h
rename to src/EWS/evolution/e-ews-search-user.h
index 4e4b8eae..ae34017c 100644
--- a/src/configuration/e-ews-search-user.h
+++ b/src/EWS/evolution/e-ews-search-user.h
@@ -8,8 +8,8 @@
#define E_EWS_SEARCH_USER_H
#include <gtk/gtk.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-item.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-item.h"
gboolean e_ews_search_user_modal (GtkWindow *parent,
EEwsConnection *conn,
diff --git a/src/configuration/e-ews-subscribe-foreign-folder.c
b/src/EWS/evolution/e-ews-subscribe-foreign-folder.c
similarity index 99%
rename from src/configuration/e-ews-subscribe-foreign-folder.c
rename to src/EWS/evolution/e-ews-subscribe-foreign-folder.c
index 5df92e99..5204d54e 100644
--- a/src/configuration/e-ews-subscribe-foreign-folder.c
+++ b/src/EWS/evolution/e-ews-subscribe-foreign-folder.c
@@ -15,7 +15,7 @@
#include "camel/camel-ews-store-summary.h"
#include "camel/camel-ews-utils.h"
-#include "server/e-ews-calendar-utils.h"
+#include "common/e-ews-calendar-utils.h"
#include "e-ews-config-utils.h"
#include "e-ews-search-user.h"
diff --git a/src/configuration/e-ews-subscribe-foreign-folder.h
b/src/EWS/evolution/e-ews-subscribe-foreign-folder.h
similarity index 100%
rename from src/configuration/e-ews-subscribe-foreign-folder.h
rename to src/EWS/evolution/e-ews-subscribe-foreign-folder.h
diff --git a/src/configuration/e-mail-config-ews-autodiscover.c
b/src/EWS/evolution/e-mail-config-ews-autodiscover.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-autodiscover.c
rename to src/EWS/evolution/e-mail-config-ews-autodiscover.c
index c0603489..00ea11e1 100644
--- a/src/configuration/e-mail-config-ews-autodiscover.c
+++ b/src/EWS/evolution/e-mail-config-ews-autodiscover.c
@@ -14,8 +14,8 @@
#include <shell/e-shell.h>
#include <mail/e-mail-config-service-page.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
#define E_MAIL_CONFIG_EWS_AUTODISCOVER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/src/configuration/e-mail-config-ews-autodiscover.h
b/src/EWS/evolution/e-mail-config-ews-autodiscover.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-autodiscover.h
rename to src/EWS/evolution/e-mail-config-ews-autodiscover.h
diff --git a/src/configuration/e-mail-config-ews-backend.c b/src/EWS/evolution/e-mail-config-ews-backend.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-backend.c
rename to src/EWS/evolution/e-mail-config-ews-backend.c
index 8c9d081b..abdabf27 100644
--- a/src/configuration/e-mail-config-ews-backend.c
+++ b/src/EWS/evolution/e-mail-config-ews-backend.c
@@ -16,7 +16,7 @@
#include <mail/e-mail-config-auth-check.h>
#include <mail/e-mail-config-receiving-page.h>
-#include "server/camel-ews-settings.h"
+#include "common/camel-ews-settings.h"
#include "e-mail-config-ews-autodiscover.h"
#include "e-ews-config-utils.h"
diff --git a/src/configuration/e-mail-config-ews-backend.h b/src/EWS/evolution/e-mail-config-ews-backend.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-backend.h
rename to src/EWS/evolution/e-mail-config-ews-backend.h
diff --git a/src/configuration/e-mail-config-ews-delegates-page.c
b/src/EWS/evolution/e-mail-config-ews-delegates-page.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-delegates-page.c
rename to src/EWS/evolution/e-mail-config-ews-delegates-page.c
index 1872f2e2..e95e29c8 100644
--- a/src/configuration/e-mail-config-ews-delegates-page.c
+++ b/src/EWS/evolution/e-mail-config-ews-delegates-page.c
@@ -15,10 +15,10 @@
#include <e-util/e-util.h>
#include <mail/e-mail-backend.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
-#include "server/e-ews-oof-settings.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-ews-oof-settings.h"
#include "camel/camel-ews-store.h"
#include "e-ews-config-utils.h"
diff --git a/src/configuration/e-mail-config-ews-delegates-page.h
b/src/EWS/evolution/e-mail-config-ews-delegates-page.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-delegates-page.h
rename to src/EWS/evolution/e-mail-config-ews-delegates-page.h
diff --git a/src/configuration/e-mail-config-ews-folder-sizes-page.c
b/src/EWS/evolution/e-mail-config-ews-folder-sizes-page.c
similarity index 100%
rename from src/configuration/e-mail-config-ews-folder-sizes-page.c
rename to src/EWS/evolution/e-mail-config-ews-folder-sizes-page.c
diff --git a/src/configuration/e-mail-config-ews-folder-sizes-page.h
b/src/EWS/evolution/e-mail-config-ews-folder-sizes-page.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-folder-sizes-page.h
rename to src/EWS/evolution/e-mail-config-ews-folder-sizes-page.h
diff --git a/src/configuration/e-mail-config-ews-gal.c b/src/EWS/evolution/e-mail-config-ews-gal.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-gal.c
rename to src/EWS/evolution/e-mail-config-ews-gal.c
index 4a0eb6c3..d9e32dc4 100644
--- a/src/configuration/e-mail-config-ews-gal.c
+++ b/src/EWS/evolution/e-mail-config-ews-gal.c
@@ -12,7 +12,7 @@
#include <mail/e-mail-config-provider-page.h>
-#include "server/camel-ews-settings.h"
+#include "common/camel-ews-settings.h"
#include "e-mail-config-ews-oal-combo-box.h"
diff --git a/src/configuration/e-mail-config-ews-gal.h b/src/EWS/evolution/e-mail-config-ews-gal.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-gal.h
rename to src/EWS/evolution/e-mail-config-ews-gal.h
diff --git a/src/configuration/e-mail-config-ews-notebook.c b/src/EWS/evolution/e-mail-config-ews-notebook.c
similarity index 100%
rename from src/configuration/e-mail-config-ews-notebook.c
rename to src/EWS/evolution/e-mail-config-ews-notebook.c
diff --git a/src/configuration/e-mail-config-ews-notebook.h b/src/EWS/evolution/e-mail-config-ews-notebook.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-notebook.h
rename to src/EWS/evolution/e-mail-config-ews-notebook.h
diff --git a/src/configuration/e-mail-config-ews-oal-combo-box.c
b/src/EWS/evolution/e-mail-config-ews-oal-combo-box.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-oal-combo-box.c
rename to src/EWS/evolution/e-mail-config-ews-oal-combo-box.c
index 4ef8f79b..9c61787f 100644
--- a/src/configuration/e-mail-config-ews-oal-combo-box.c
+++ b/src/EWS/evolution/e-mail-config-ews-oal-combo-box.c
@@ -8,8 +8,8 @@
#include <mail/e-mail-config-service-page.h>
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
#include "e-ews-config-utils.h"
#include "e-mail-config-ews-oal-combo-box.h"
diff --git a/src/configuration/e-mail-config-ews-oal-combo-box.h
b/src/EWS/evolution/e-mail-config-ews-oal-combo-box.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-oal-combo-box.h
rename to src/EWS/evolution/e-mail-config-ews-oal-combo-box.h
diff --git a/src/configuration/e-mail-config-ews-offline-options.c
b/src/EWS/evolution/e-mail-config-ews-offline-options.c
similarity index 100%
rename from src/configuration/e-mail-config-ews-offline-options.c
rename to src/EWS/evolution/e-mail-config-ews-offline-options.c
diff --git a/src/configuration/e-mail-config-ews-offline-options.h
b/src/EWS/evolution/e-mail-config-ews-offline-options.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-offline-options.h
rename to src/EWS/evolution/e-mail-config-ews-offline-options.h
diff --git a/src/configuration/e-mail-config-ews-ooo-page.c b/src/EWS/evolution/e-mail-config-ews-ooo-page.c
similarity index 99%
rename from src/configuration/e-mail-config-ews-ooo-page.c
rename to src/EWS/evolution/e-mail-config-ews-ooo-page.c
index b0e3e75e..5cf74985 100644
--- a/src/configuration/e-mail-config-ews-ooo-page.c
+++ b/src/EWS/evolution/e-mail-config-ews-ooo-page.c
@@ -14,10 +14,10 @@
#include <e-util/e-util.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-connection-utils.h"
-#include "server/e-ews-oof-settings.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-ews-oof-settings.h"
#include "e-ews-config-utils.h"
diff --git a/src/configuration/e-mail-config-ews-ooo-page.h b/src/EWS/evolution/e-mail-config-ews-ooo-page.h
similarity index 100%
rename from src/configuration/e-mail-config-ews-ooo-page.h
rename to src/EWS/evolution/e-mail-config-ews-ooo-page.h
diff --git a/src/configuration/module-ews-configuration.c b/src/EWS/evolution/module-ews-configuration.c
similarity index 94%
rename from src/configuration/module-ews-configuration.c
rename to src/EWS/evolution/module-ews-configuration.c
index 49ca159a..978e7cb3 100644
--- a/src/configuration/module-ews-configuration.c
+++ b/src/EWS/evolution/module-ews-configuration.c
@@ -24,9 +24,9 @@
#include "e-ews-photo-source.h"
#include "e-ews-config-ui-extension.h"
-#include "server/camel-sasl-xoauth2-office365.h"
-#include "server/e-oauth2-service-office365.h"
-#include "server/e-source-ews-folder.h"
+#include "common/camel-sasl-xoauth2-office365.h"
+#include "common/e-oauth2-service-office365.h"
+#include "common/e-source-ews-folder.h"
/* Module Entry Points */
void e_module_load (GTypeModule *type_module);
diff --git a/src/configuration/module-ews-configuration.error.xml
b/src/EWS/evolution/module-ews-configuration.error.xml
similarity index 100%
rename from src/configuration/module-ews-configuration.error.xml
rename to src/EWS/evolution/module-ews-configuration.error.xml
diff --git a/src/collection/CMakeLists.txt b/src/EWS/registry/CMakeLists.txt
similarity index 100%
rename from src/collection/CMakeLists.txt
rename to src/EWS/registry/CMakeLists.txt
diff --git a/src/collection/e-ews-backend-factory.c b/src/EWS/registry/e-ews-backend-factory.c
similarity index 100%
rename from src/collection/e-ews-backend-factory.c
rename to src/EWS/registry/e-ews-backend-factory.c
diff --git a/src/collection/e-ews-backend-factory.h b/src/EWS/registry/e-ews-backend-factory.h
similarity index 100%
rename from src/collection/e-ews-backend-factory.h
rename to src/EWS/registry/e-ews-backend-factory.h
diff --git a/src/collection/e-ews-backend.c b/src/EWS/registry/e-ews-backend.c
similarity index 99%
rename from src/collection/e-ews-backend.c
rename to src/EWS/registry/e-ews-backend.c
index 4669ad39..eaa82ba3 100644
--- a/src/collection/e-ews-backend.c
+++ b/src/EWS/registry/e-ews-backend.c
@@ -10,8 +10,8 @@
#include <glib/gi18n-lib.h>
-#include "server/e-ews-connection-utils.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-ews-connection-utils.h"
+#include "common/e-source-ews-folder.h"
#define E_EWS_BACKEND_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/src/collection/e-ews-backend.h b/src/EWS/registry/e-ews-backend.h
similarity index 98%
rename from src/collection/e-ews-backend.h
rename to src/EWS/registry/e-ews-backend.h
index 9bcc6a2f..82c72844 100644
--- a/src/collection/e-ews-backend.h
+++ b/src/EWS/registry/e-ews-backend.h
@@ -9,7 +9,7 @@
#include <libebackend/libebackend.h>
-#include "server/e-ews-connection.h"
+#include "common/e-ews-connection.h"
/* Standard GObject macros */
#define E_TYPE_EWS_BACKEND \
diff --git a/src/collection/module-ews-backend.c b/src/EWS/registry/module-ews-backend.c
similarity index 91%
rename from src/collection/module-ews-backend.c
rename to src/EWS/registry/module-ews-backend.c
index 41c98c10..6e899abb 100644
--- a/src/collection/module-ews-backend.c
+++ b/src/EWS/registry/module-ews-backend.c
@@ -8,8 +8,8 @@
#include <glib/gi18n-lib.h>
-#include "server/e-oauth2-service-office365.h"
-#include "server/e-source-ews-folder.h"
+#include "common/e-oauth2-service-office365.h"
+#include "common/e-source-ews-folder.h"
#include "e-ews-backend.h"
#include "e-ews-backend-factory.h"
diff --git a/src/Microsoft365/CMakeLists.txt b/src/Microsoft365/CMakeLists.txt
new file mode 100644
index 00000000..2c187fec
--- /dev/null
+++ b/src/Microsoft365/CMakeLists.txt
@@ -0,0 +1,64 @@
+macro(add_simple_module_m365 _name _sourcesvar _depsvar _defsvar _cflagsvar _incdirsvar _ldflagsvar
_destination)
+ set(DEPENDENCIES
+ evolution-microsoft365
+ )
+
+ set(SOURCES
+ ${${_sourcesvar}}
+ )
+
+ add_library(${_name} MODULE
+ ${SOURCES}
+ )
+
+ set_target_properties(${_name} PROPERTIES
+ PREFIX ""
+ )
+
+ add_dependencies(${_name}
+ ${DEPENDENCIES}
+ ${${_depsvar}}
+ )
+
+ target_compile_definitions(${_name} PRIVATE
+ -DG_LOG_DOMAIN=\"${_name}\"
+ -DM365_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ ${${_defsvar}}
+ )
+
+ target_compile_options(${_name} PUBLIC
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBEDATASERVER_CFLAGS}
+ ${SOUP_CFLAGS}
+ ${${_cflagsvar}}
+ )
+
+ target_include_directories(${_name} PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBEDATASERVER_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+ ${${_incdirsvar}}
+ )
+
+ target_link_libraries(${_name}
+ ${DEPENDENCIES}
+ ${${_depsvar}}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBEDATASERVER_LDFLAGS}
+ ${SOUP_LDFLAGS}
+ ${${_ldflagsvar}}
+ )
+
+ install(TARGETS ${_name}
+ DESTINATION ${_destination}
+ )
+endmacro(add_simple_module_m365)
+
+add_subdirectory(addressbook)
+add_subdirectory(calendar)
+add_subdirectory(camel)
+add_subdirectory(common)
+add_subdirectory(evolution)
+add_subdirectory(registry)
diff --git a/src/Microsoft365/addressbook/CMakeLists.txt b/src/Microsoft365/addressbook/CMakeLists.txt
new file mode 100644
index 00000000..2789a140
--- /dev/null
+++ b/src/Microsoft365/addressbook/CMakeLists.txt
@@ -0,0 +1,65 @@
+set(DEPENDENCIES
+ evolution-microsoft365
+)
+
+set(SOURCES
+ e-book-backend-m365.c
+ e-book-backend-m365.h
+ e-book-backend-m365-factory.c
+)
+
+add_library(ebookbackendmicrosoft365 MODULE
+ ${SOURCES}
+)
+
+add_dependencies(ebookbackendmicrosoft365
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(ebookbackendmicrosoft365 PRIVATE
+ -DG_LOG_DOMAIN=\"ebookbackendmicrosoft365\"
+ -DBACKENDDIR=\"${ebook_backenddir}\"
+ -DM365_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+)
+
+target_compile_options(ebookbackendmicrosoft365 PUBLIC
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBEBOOK_CFLAGS}
+ ${LIBEDATABOOK_CFLAGS}
+ ${LIBEDATASERVER_CFLAGS}
+ ${LIBICAL_GLIB_CFLAGS}
+ ${MSPACK_CFLAGS}
+ ${SOUP_CFLAGS}
+)
+
+target_include_directories(ebookbackendmicrosoft365 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/src/Microsoft365
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBEBOOK_INCLUDE_DIRS}
+ ${LIBEDATABOOK_INCLUDE_DIRS}
+ ${LIBEDATASERVER_INCLUDE_DIRS}
+ ${LIBICAL_GLIB_INCLUDE_DIRS}
+ ${MSPACK_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+)
+
+target_link_libraries(ebookbackendmicrosoft365
+ ${DEPENDENCIES}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBEBOOK_LDFLAGS}
+ ${LIBEDATABOOK_LDFLAGS}
+ ${LIBEDATASERVER_LDFLAGS}
+ ${LIBICAL_GLIB_LDFLAGS}
+ ${MATH_LDFLAGS}
+ ${MSPACK_LDFLAGS}
+ ${SOUP_LDFLAGS}
+)
+
+install(TARGETS ebookbackendmicrosoft365
+ DESTINATION ${ebook_backenddir}
+)
diff --git a/src/Microsoft365/addressbook/e-book-backend-m365-factory.c
b/src/Microsoft365/addressbook/e-book-backend-m365-factory.c
new file mode 100644
index 00000000..fcef9c21
--- /dev/null
+++ b/src/Microsoft365/addressbook/e-book-backend-m365-factory.c
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libedata-book/libedata-book.h>
+
+#include "common/e-oauth2-service-microsoft365.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-book-backend-m365.h"
+
+typedef EBookBackendFactory EBookBackendM365Factory;
+typedef EBookBackendFactoryClass EBookBackendM365FactoryClass;
+
+static EModule *e_module;
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_book_backend_m365_factory_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (EBookBackendM365Factory, e_book_backend_m365_factory, E_TYPE_BOOK_BACKEND_FACTORY)
+
+static void
+e_book_backend_m365_factory_class_init (EBookBackendFactoryClass *class)
+{
+ EBackendFactoryClass *backend_factory_class;
+
+ backend_factory_class = E_BACKEND_FACTORY_CLASS (class);
+ backend_factory_class->e_module = e_module;
+ backend_factory_class->share_subprocess = TRUE;
+
+ class->factory_name = "microsoft365";
+ class->backend_type = E_TYPE_BOOK_BACKEND_M365;
+}
+
+static void
+e_book_backend_m365_factory_class_finalize (EBookBackendFactoryClass *class)
+{
+}
+
+static void
+e_book_backend_m365_factory_init (EBookBackendFactory *factory)
+{
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, M365_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ e_module = E_MODULE (type_module);
+
+ e_oauth2_service_microsoft365_type_register (type_module);
+ e_source_m365_folder_type_register (type_module);
+
+ if (g_strcmp0 (g_getenv ("ENABLE_M365"), "1") == 0)
+ e_book_backend_m365_factory_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+ e_module = NULL;
+}
diff --git a/src/Microsoft365/addressbook/e-book-backend-m365.c
b/src/Microsoft365/addressbook/e-book-backend-m365.c
new file mode 100644
index 00000000..bfe19958
--- /dev/null
+++ b/src/Microsoft365/addressbook/e-book-backend-m365.c
@@ -0,0 +1,1933 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedata-book/libedata-book.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-book-backend-m365.h"
+
+#ifdef G_OS_WIN32
+#ifdef gmtime_r
+#undef gmtime_r
+#endif
+
+/* The gmtime() in Microsoft's C library is MT-safe */
+#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+#endif
+
+#define EC_ERROR_EX(_code,_msg) e_client_error_create (_code, _msg)
+#define EBC_ERROR_EX(_code,_msg) e_book_client_error_create (_code, _msg)
+
+#define LOCK(_bb) g_rec_mutex_lock (&_bb->priv->property_lock)
+#define UNLOCK(_bb) g_rec_mutex_unlock (&_bb->priv->property_lock)
+
+struct _EBookBackendM365Private {
+ GRecMutex property_lock;
+ EM365Connection *cnc;
+ gchar *folder_id;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (EBookBackendM365, e_book_backend_m365, E_TYPE_BOOK_META_BACKEND)
+
+static void
+ebb_m365_contact_get_string_attribute (EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ const gchar * (*m365_get_func) (EM365Contact *contact))
+{
+ e_contact_set (inout_contact, field_id, m365_get_func (m365_contact));
+}
+
+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))
+{
+ const gchar *new_value, *old_value;
+
+ g_return_if_fail (m365_add_func != NULL);
+
+ new_value = e_contact_get_const (new_contact, field_id);
+ old_value = old_contact ? e_contact_get_const (old_contact, field_id) : NULL;
+
+ if (g_strcmp0 (new_value, old_value) != 0)
+ m365_add_func (builder, new_value);
+}
+
+static gboolean
+ebb_m365_contact_get_rev (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar time_string[100] = { 0 };
+ struct tm stm;
+ time_t value;
+
+ value = e_m365_contact_get_last_modified_date_time (m365_contact);
+
+ if (value <= (time_t) 0)
+ value = time (NULL);
+
+ gmtime_r (&value, &stm);
+ strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", &stm);
+
+ e_contact_set (inout_contact, field_id, time_string);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_birthday (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ time_t value;
+
+ value = e_m365_contact_get_birthday (m365_contact);
+
+ if (value > (time_t) 0) {
+ EContactDate dt;
+ struct tm stm;
+
+ gmtime_r (&value, &stm);
+
+ dt.year = stm.tm_year + 1900;
+ dt.month = stm.tm_mon + 1;
+ dt.day = stm.tm_mday;
+
+ e_contact_set (inout_contact, field_id, &dt);
+ }
+
+ return TRUE;
+}
+
+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)
+{
+ EContactDate *old_dt = NULL;
+ EContactDate *new_dt = NULL;
+
+ new_dt = e_contact_get (new_contact, field_id);
+ old_dt = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!e_contact_date_equal (new_dt, old_dt)) {
+ if (new_dt) {
+ GDateTime *gdt;
+ time_t value = (time_t) 0;
+
+ gdt = g_date_time_new_local (new_dt->year, new_dt->month, new_dt->day, 11, 59, 0.0);
+
+ if (gdt) {
+ value = g_date_time_to_unix (gdt);
+ value = value - (value % (24 * 60 * 60));
+ value = value + (((12 * 60) - 1) * 60);
+
+ g_date_time_unref (gdt);
+ }
+
+ e_m365_contact_add_birthday (builder, value);
+ } else {
+ e_m365_contact_add_birthday (builder, (time_t) 0);
+ }
+ }
+
+ e_contact_date_free (new_dt);
+ e_contact_date_free (old_dt);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_address (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365PhysicalAddress *phys_address = NULL;
+
+ if (field_id == E_CONTACT_ADDRESS_WORK)
+ phys_address = e_m365_contact_get_business_address (m365_contact);
+ else if (field_id == E_CONTACT_ADDRESS_HOME)
+ phys_address = e_m365_contact_get_home_address (m365_contact);
+ else if (field_id == E_CONTACT_ADDRESS_OTHER)
+ phys_address = e_m365_contact_get_other_address (m365_contact);
+ else
+ g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
+
+ if (phys_address) {
+ EContactAddress addr;
+
+ memset (&addr, 0, sizeof (EContactAddress));
+
+ addr.locality = (gchar *) e_m365_physical_address_get_city (phys_address);
+ addr.country = (gchar *) e_m365_physical_address_get_country_or_region (phys_address);
+ addr.code = (gchar *) e_m365_physical_address_get_postal_code (phys_address);
+ addr.region = (gchar *) e_m365_physical_address_get_state (phys_address);
+ addr.street = (gchar *) e_m365_physical_address_get_street (phys_address);
+
+ if (addr.locality || addr.country || addr.code || addr.region || addr.street)
+ e_contact_set (inout_contact, field_id, &addr);
+ else
+ e_contact_set (inout_contact, field_id, NULL);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_address_equal (const EContactAddress *addr1,
+ const EContactAddress *addr2)
+{
+ if (!addr1 && !addr2)
+ return TRUE;
+
+ if ((addr1 && !addr2) || (!addr1 && addr2))
+ return FALSE;
+
+ return /* g_strcmp0 (addr1->address_format, addr2->address_format) == 0 && */
+ g_strcmp0 (addr1->po, addr2->po) == 0 &&
+ g_strcmp0 (addr1->ext, addr2->ext) == 0 &&
+ g_strcmp0 (addr1->street, addr2->street) == 0 &&
+ g_strcmp0 (addr1->locality, addr2->locality) == 0 &&
+ g_strcmp0 (addr1->region, addr2->region) == 0 &&
+ g_strcmp0 (addr1->code, addr2->code) == 0 &&
+ g_strcmp0 (addr1->country, addr2->country) == 0;
+}
+
+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)
+{
+ EContactAddress *new_addr, *old_addr;
+
+ new_addr = e_contact_get (new_contact, field_id);
+ old_addr = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!ebb_m365_contact_address_equal (new_addr, old_addr)) {
+ void (* add_func) (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street) = NULL;
+
+ if (field_id == E_CONTACT_ADDRESS_WORK)
+ add_func = e_m365_contact_add_business_address;
+ else if (field_id == E_CONTACT_ADDRESS_HOME)
+ add_func = e_m365_contact_add_home_address;
+ else if (field_id == E_CONTACT_ADDRESS_OTHER)
+ add_func = e_m365_contact_add_other_address;
+ else
+ g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute
(field_id));
+
+ if (add_func) {
+ if (new_addr) {
+ add_func (builder, new_addr->locality, new_addr->country, new_addr->code,
new_addr->region, new_addr->street);
+ } else {
+ add_func (builder, NULL, NULL, NULL, NULL, NULL);
+ }
+ }
+ }
+
+ e_contact_address_free (new_addr);
+ e_contact_address_free (old_addr);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_string_values_equal (GSList *new_values, /* const gchar * */
+ GSList *old_values) /* const gchar * */
+{
+ GHashTable *values;
+ GSList *link;
+ gboolean equal = TRUE;
+
+ if (g_slist_length (new_values) != g_slist_length (old_values))
+ return FALSE;
+
+ values = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (link = new_values; link; link = g_slist_next (link)) {
+ gchar *value = link->data;
+
+ if (value)
+ g_hash_table_add (values, value);
+ }
+
+ for (link = old_values; link && equal; link = g_slist_next (link)) {
+ const gchar *value = link->data;
+
+ if (value)
+ equal = g_hash_table_remove (values, value);
+ }
+
+ equal = equal && !g_hash_table_size (values);
+
+ g_hash_table_destroy (values);
+
+ return equal;
+}
+
+static gboolean
+ebb_m365_string_list_values_equal (GList *new_values, /* const gchar * */
+ GList *old_values) /* const gchar * */
+{
+ GHashTable *values;
+ GList *link;
+ gboolean equal = TRUE;
+
+ if (g_list_length (new_values) != g_list_length (old_values))
+ return FALSE;
+
+ values = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (link = new_values; link; link = g_list_next (link)) {
+ gchar *value = link->data;
+
+ if (value)
+ g_hash_table_add (values, value);
+ }
+
+ for (link = old_values; link && equal; link = g_list_next (link)) {
+ const gchar *value = link->data;
+
+ if (value)
+ equal = g_hash_table_remove (values, value);
+ }
+
+ equal = equal && !g_hash_table_size (values);
+
+ g_hash_table_destroy (values);
+
+ return equal;
+}
+
+static gboolean
+ebb_m365_contact_get_phone (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonArray *values = NULL;
+ const gchar *type_val = NULL;
+
+ if (field_id == E_CONTACT_PHONE_BUSINESS) {
+ values = e_m365_contact_get_business_phones (m365_contact);
+ type_val = "WORK";
+ } else if (field_id == E_CONTACT_PHONE_HOME) {
+ values = e_m365_contact_get_home_phones (m365_contact);
+ type_val = "HOME";
+ } else {
+ g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
+ }
+
+ if (values) {
+ EVCard *vcard = E_VCARD (inout_contact);
+ guint ii, len;
+
+ len = json_array_get_length (values);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *str = json_array_get_string_element (values, len - ii - 1);
+
+ if (str && *str) {
+ EVCardAttributeParam *param;
+ EVCardAttribute *attr;
+
+ attr = e_vcard_attribute_new (NULL, EVC_TEL);
+ param = e_vcard_attribute_param_new (EVC_TYPE);
+
+ e_vcard_attribute_add_param_with_value (attr, param, type_val);
+ e_vcard_add_attribute_with_value (vcard, attr, str);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static GSList * /* gchar * */
+ebb_m365_extract_phones (EContact *contact,
+ const gchar *only_type) /* NULL for anything but known types */
+{
+ GSList *phones = NULL;
+ GList *attrs, *link;
+
+ if (!contact)
+ return NULL;
+
+ attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+ for (link = attrs; link; link = g_list_next (link)) {
+ EVCardAttribute *attr = link->data;
+ gboolean use_it = FALSE;
+
+ if (!attr || !e_vcard_attribute_get_name (attr) ||
+ g_ascii_strcasecmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
+ continue;
+
+ if (only_type) {
+ use_it = e_vcard_attribute_has_type (attr, only_type);
+ } else {
+ use_it = !e_vcard_attribute_has_type (attr, "WORK") &&
+ !e_vcard_attribute_has_type (attr, "CELL");
+ }
+
+ if (use_it)
+ phones = g_slist_prepend (phones, e_vcard_attribute_get_value (attr));
+ }
+
+ return g_slist_reverse (phones);
+}
+
+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)
+{
+ void (* begin_func) (JsonBuilder *builder) = NULL;
+ void (* end_func) (JsonBuilder *builder) = NULL;
+ void (* add_func) (JsonBuilder *builder, const gchar *value) = NULL;
+ const gchar *type_val = NULL;
+ GSList *new_values, *old_values;
+
+ if (field_id == E_CONTACT_PHONE_BUSINESS) {
+ begin_func = e_m365_contact_begin_business_phones;
+ end_func = e_m365_contact_end_business_phones;
+ add_func = e_m365_contact_add_business_phone;
+ type_val = "WORK";
+ } else if (field_id == E_CONTACT_PHONE_HOME) {
+ begin_func = e_m365_contact_begin_home_phones;
+ end_func = e_m365_contact_end_home_phones;
+ add_func = e_m365_contact_add_home_phone;
+ type_val = NULL; /* everything else is treated as "HOME" phone */
+ } else {
+ g_warning ("%s: Uncaught field '%s'", G_STRFUNC, e_contact_vcard_attribute (field_id));
+ }
+
+ new_values = ebb_m365_extract_phones (new_contact, type_val);
+ old_values = ebb_m365_extract_phones (old_contact, type_val);
+
+ if (!ebb_m365_string_values_equal (new_values, old_values)) {
+ GSList *link;
+
+ begin_func (builder);
+
+ for (link = new_values; link; link = g_slist_next (link)) {
+ const gchar *value = link->data;
+
+ add_func (builder, value);
+ }
+
+ end_func (builder);
+ }
+
+ g_slist_free_full (new_values, g_free);
+ g_slist_free_full (old_values, g_free);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_categories (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonArray *values;
+
+ values = e_m365_contact_get_categories (m365_contact);
+
+ if (values) {
+ GString *categories_str = NULL;
+ guint ii, len;
+
+ len = json_array_get_length (values);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *str = json_array_get_string_element (values, ii);
+
+ if (str && *str) {
+ if (!categories_str) {
+ categories_str = g_string_new (str);
+ } else {
+ g_string_append_c (categories_str, ',');
+ g_string_append (categories_str, str);
+ }
+ }
+ }
+
+ if (categories_str) {
+ e_contact_set (inout_contact, field_id, categories_str->str);
+ g_string_free (categories_str, TRUE);
+ }
+ }
+
+ return TRUE;
+}
+
+static GSList *
+ebb_m365_extract_categories (EContact *contact,
+ EContactField field_id)
+{
+ GSList *categories = NULL;
+ const gchar *str;
+
+ if (!contact)
+ return NULL;
+
+ str = e_contact_get_const (contact, field_id);
+
+ if (str && *str) {
+ gchar **split_str;
+ gint ii;
+
+ split_str = g_strsplit (str, ",", -1);
+
+ for (ii = 0; split_str && split_str[ii]; ii++) {
+ gchar *item = split_str[ii];
+
+ if (item && *item)
+ categories = g_slist_prepend (categories, item);
+ else
+ g_free (item);
+
+ split_str[ii] = NULL;
+ }
+
+ g_free (split_str);
+ }
+
+ return g_slist_reverse (categories);
+}
+
+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)
+{
+ GSList *new_values, *old_values;
+
+ new_values = ebb_m365_extract_categories (new_contact, field_id);
+ old_values = ebb_m365_extract_categories (old_contact, field_id);
+
+ if (!ebb_m365_string_values_equal (new_values, old_values)) {
+ GSList *link;
+
+ e_m365_contact_begin_categories (builder);
+
+ for (link = new_values; link; link = g_slist_next (link)) {
+ const gchar *value = link->data;
+
+ e_m365_contact_add_category (builder, value);
+ }
+
+ e_m365_contact_end_categories (builder);
+ }
+
+ g_slist_free_full (new_values, g_free);
+ g_slist_free_full (old_values, g_free);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_emails (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonArray *values;
+
+ values = e_m365_contact_get_email_addresses (m365_contact);
+
+ if (values) {
+ EVCard *vcard = E_VCARD (inout_contact);
+ guint ii, len;
+
+ len = json_array_get_length (values);
+
+ for (ii = 0; ii < len; ii++) {
+ EM365EmailAddress *address = json_array_get_object_element (values, len - ii - 1);
+
+ if (address) {
+ EVCardAttribute *attr;
+
+ attr = e_vcard_attribute_new (NULL, EVC_EMAIL);
+ e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new
(EVC_TYPE), "OTHER");
+
+ if (g_strcmp0 (e_m365_email_address_get_name (address),
e_m365_email_address_get_address (address)) == 0) {
+ e_vcard_add_attribute_with_value (vcard, attr,
e_m365_email_address_get_address (address));
+ } else {
+ gchar *formatted;
+
+ formatted = camel_internet_address_format_address (
+ e_m365_email_address_get_name (address),
+ e_m365_email_address_get_address (address));
+
+ if (formatted && *formatted)
+ e_vcard_add_attribute_with_value (vcard, attr, formatted);
+ else
+ e_vcard_attribute_free (attr);
+
+ g_free (formatted);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_parse_qp_email (const gchar *string,
+ gchar **name,
+ gchar **email)
+{
+ struct _camel_header_address *address;
+ gboolean res = FALSE;
+
+ address = camel_header_address_decode (string, "UTF-8");
+
+ if (address) {
+ /* report success only when we have filled both name and email address */
+ if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name &&
address->v.addr && *address->v.addr) {
+ *name = g_strdup (address->name);
+ *email = g_strdup (address->v.addr);
+ res = TRUE;
+ }
+
+ camel_header_address_unref (address);
+ }
+
+ if (!res) {
+ CamelInternetAddress *addr = camel_internet_address_new ();
+ const gchar *const_name = NULL, *const_email = NULL;
+
+ if (camel_address_unformat (CAMEL_ADDRESS (addr), string) == 1 &&
+ camel_internet_address_get (addr, 0, &const_name, &const_email) &&
+ const_name && *const_name && const_email && *const_email) {
+ *name = g_strdup (const_name);
+ *email = g_strdup (const_email);
+ res = TRUE;
+ }
+
+ g_clear_object (&addr);
+ }
+
+ return res;
+}
+
+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)
+{
+ GList *new_values, *old_values;
+
+ new_values = e_contact_get (new_contact, field_id);
+ old_values = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!ebb_m365_string_list_values_equal (new_values, old_values)) {
+ GList *link;
+
+ e_m365_contact_begin_email_addresses (builder);
+
+ for (link = new_values; link; link = g_list_next (link)) {
+ const gchar *value = link->data;
+ gchar *name = NULL, *address = NULL;
+
+ if (ebb_m365_parse_qp_email (value, &name, &address))
+ e_m365_add_email_address (builder, NULL, name, address);
+ else
+ e_m365_add_email_address (builder, NULL, NULL, value);
+
+ g_free (name);
+ g_free (address);
+ }
+
+ e_m365_contact_end_email_addresses (builder);
+ }
+
+ g_list_free_full (new_values, g_free);
+ g_list_free_full (old_values, g_free);
+
+ return TRUE;
+}
+
+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)
+{
+ const gchar *new_value;
+
+ ebb_m365_contact_add_string_attribute (new_contact, old_contact, field_id, builder,
e_m365_contact_add_file_as);
+
+ new_value = e_contact_get_const (new_contact, E_CONTACT_FILE_AS);
+
+ /* Set it always, to not be overwritten by server re-calculations on other property changes */
+ e_m365_contact_add_display_name (builder, new_value);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_generation (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *value;
+
+ value = e_m365_contact_get_generation (m365_contact);
+
+ if (value && *value) {
+ EContactName *name = e_contact_get (inout_contact, field_id);
+ gchar *prev;
+
+ if (!name)
+ name = e_contact_name_new ();
+
+ prev = name->suffixes;
+ name->suffixes = (gchar *) value;
+
+ e_contact_set (inout_contact, field_id, name);
+
+ name->suffixes = prev;
+ e_contact_name_free (name);
+ }
+
+ return TRUE;
+}
+
+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)
+{
+ EContactName *new_value, *old_value;
+
+ new_value = e_contact_get (new_contact, field_id);
+ old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!(new_value && old_value && g_strcmp0 (new_value->suffixes, old_value->suffixes) == 0))
+ e_m365_contact_add_generation (builder, new_value ? new_value->suffixes : NULL);
+
+ e_contact_name_free (new_value);
+ e_contact_name_free (old_value);
+
+ return TRUE;
+}
+
+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)
+{
+ JsonArray *values;
+
+ values = e_m365_contact_get_im_addresses (m365_contact);
+
+ if (values) {
+ EVCard *vcard = E_VCARD (inout_contact);
+ const gchar *field_name = e_contact_vcard_attribute (field_id);
+ guint ii, len;
+
+ len = json_array_get_length (values);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *str = json_array_get_string_element (values, len - ii - 1);
+
+ if (str && *str) {
+ EVCardAttribute *attr;
+
+ attr = e_vcard_attribute_new (NULL, field_name);
+
+ e_vcard_add_attribute_with_value (vcard, attr, str);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static GSList * /* gchar * */
+ebb_m365_extract_im_addresses (EContact *contact)
+{
+ GSList *ims = NULL;
+ GList *attrs, *link;
+
+ if (!contact)
+ return NULL;
+
+ attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+ for (link = attrs; link; link = g_list_next (link)) {
+ EVCardAttribute *attr = link->data;
+ const gchar *name;
+
+ if (!attr)
+ continue;
+
+ name = e_vcard_attribute_get_name (attr);
+
+ if (!name || (
+ g_ascii_strcasecmp (name, EVC_X_GOOGLE_TALK) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_SKYPE) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_GADUGADU) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_AIM) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_GROUPWISE) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_JABBER) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_YAHOO) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_MSN) != 0 &&
+ g_ascii_strcasecmp (name, EVC_X_ICQ) != 0))
+ continue;
+
+ ims = g_slist_prepend (ims, e_vcard_attribute_get_value (attr));
+ }
+
+ return g_slist_reverse (ims);
+}
+
+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)
+{
+ GSList *new_values, *old_values;
+
+ new_values = ebb_m365_extract_im_addresses (new_contact);
+ old_values = ebb_m365_extract_im_addresses (old_contact);
+
+ if (!ebb_m365_string_values_equal (new_values, old_values)) {
+ GSList *link;
+
+ e_m365_contact_begin_im_addresses (builder);
+
+ for (link = new_values; link; link = g_slist_next (link)) {
+ const gchar *value = link->data;
+
+ if (value && *value)
+ e_m365_contact_add_im_address (builder, value);
+ }
+
+ e_m365_contact_end_im_addresses (builder);
+ }
+
+ g_slist_free_full (new_values, g_free);
+ g_slist_free_full (old_values, g_free);
+
+ return TRUE;
+}
+
+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)
+{
+ const gchar *value;
+
+ value = e_m365_contact_get_middle_name (m365_contact);
+
+ if (value && *value) {
+ EContactName *name = e_contact_get (inout_contact, field_id);
+ gchar *prev;
+
+ if (!name)
+ name = e_contact_name_new ();
+
+ prev = name->additional;
+ name->additional = (gchar *) value;
+
+ e_contact_set (inout_contact, field_id, name);
+
+ name->additional = prev;
+ e_contact_name_free (name);
+ }
+
+ return TRUE;
+}
+
+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)
+{
+ EContactName *new_value, *old_value;
+
+ new_value = e_contact_get (new_contact, field_id);
+ old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!(new_value && old_value && g_strcmp0 (new_value->additional, old_value->additional) == 0))
+ e_m365_contact_add_middle_name (builder, new_value ? new_value->additional : NULL);
+
+ e_contact_name_free (new_value);
+ e_contact_name_free (old_value);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_title (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *value;
+
+ value = e_m365_contact_get_title (m365_contact);
+
+ if (value && *value) {
+ EContactName *name = e_contact_get (inout_contact, field_id);
+ gchar *prev;
+
+ if (!name)
+ name = e_contact_name_new ();
+
+ prev = name->prefixes;
+ name->prefixes = (gchar *) value;
+
+ e_contact_set (inout_contact, field_id, name);
+
+ name->prefixes = prev;
+ e_contact_name_free (name);
+ }
+
+ return TRUE;
+}
+
+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)
+{
+ EContactName *new_value, *old_value;
+
+ new_value = e_contact_get (new_contact, field_id);
+ old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!(new_value && old_value && g_strcmp0 (new_value->prefixes, old_value->prefixes) == 0))
+ e_m365_contact_add_title (builder, new_value ? new_value->prefixes : NULL);
+
+ e_contact_name_free (new_value);
+ e_contact_name_free (old_value);
+
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_get_photo (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GByteArray *photo_data = NULL;
+ GError *local_error = NULL;
+
+ LOCK (bbm365);
+
+ if (e_m365_connection_get_contact_photo_sync (cnc, NULL, bbm365->priv->folder_id,
+ e_m365_contact_get_id (m365_contact), &photo_data, cancellable, &local_error) &&
+ photo_data && photo_data->len) {
+ EContactPhoto *photo;
+
+ photo = e_contact_photo_new ();
+ e_contact_photo_set_inlined (photo, photo_data->data, photo_data->len);
+ e_contact_photo_set_mime_type (photo, "image/jpeg");
+ e_contact_set (inout_contact, field_id, photo);
+ e_contact_photo_free (photo);
+ }
+
+ UNLOCK (bbm365);
+
+ if (photo_data)
+ g_byte_array_unref (photo_data);
+ g_clear_error (&local_error);
+
+ /* Even it could fail, ignore it and read as many contacts as possible, rather than stop on the first
error */
+ return TRUE;
+}
+
+static gboolean
+ebb_m365_contact_photo_equal (EContactPhoto *photo1,
+ EContactPhoto *photo2)
+{
+ const guchar *data1, *data2;
+ gsize len1 = 0, len2 = 0;
+
+ if (!photo1 && !photo2)
+ return TRUE;
+
+ if ((photo1 && !photo2) || (!photo1 && photo2))
+ return FALSE;
+
+ data1 = e_contact_photo_get_inlined (photo1, &len1);
+ data2 = e_contact_photo_get_inlined (photo2, &len2);
+
+ if (!data1 && !data2)
+ return TRUE;
+
+ return len1 == len2 &&
+ memcmp (data1, data2, len1) == 0;
+}
+
+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)
+{
+ EContactPhoto *new_value, *old_value;
+
+ new_value = e_contact_get (new_contact, field_id);
+ old_value = old_contact ? e_contact_get (old_contact, field_id) : NULL;
+
+ if (!ebb_m365_contact_photo_equal (new_value, old_value)) {
+ GByteArray *jpeg_photo = NULL, tmp;
+ GError *local_error = NULL;
+
+ if (new_value) {
+ gsize len = 0;
+
+ tmp.data = (guchar *) e_contact_photo_get_inlined (new_value, &len);
+
+ if (len && tmp.data) {
+ tmp.len = len;
+ jpeg_photo = &tmp;
+ }
+ }
+
+ LOCK (bbm365);
+
+ if (!e_m365_connection_update_contact_photo_sync (bbm365->priv->cnc, NULL,
bbm365->priv->folder_id,
+ m365_id ? m365_id : e_contact_get_const (new_contact, E_CONTACT_UID), jpeg_photo,
cancellable, &local_error)) {
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ }
+ }
+
+ UNLOCK (bbm365);
+
+ g_clear_error (&local_error);
+ }
+
+ e_contact_photo_free (new_value);
+ e_contact_photo_free (old_value);
+
+ return TRUE;
+}
+
+#define STRING_FIELD(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, addfn, NULL }
+#define COMPLEX_FIELD(fldid, getfn, addfn) { fldid, FALSE, NULL, getfn, NULL, addfn }
+#define COMPLEX_FIELD_2(fldid, getfn, addfn) { fldid, TRUE, NULL, getfn, NULL, addfn }
+#define COMPLEX_ADDFN(fldid, getfn, addfn) { fldid, FALSE, getfn, NULL, NULL, addfn }
+
+struct _mappings {
+ EContactField field_id;
+ gboolean add_in_second_go;
+ const gchar * (* m365_get_func) (EM365Contact *m365_contact);
+ gboolean (* get_func) (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EContact *inout_contact,
+ EContactField field_id,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error);
+ void (* m365_add_func) (JsonBuilder *builder,
+ const gchar *value);
+ gboolean (* add_func) (EBookBackendM365 *bbm365,
+ EContact *new_contact,
+ EContact *old_contact, /* nullable */
+ EContactField field_id,
+ const gchar *m365_id,
+ JsonBuilder *builder,
+ GCancellable *cancellable,
+ GError **error);
+} mappings[] = {
+ STRING_FIELD (E_CONTACT_UID, e_m365_contact_get_id, NULL),
+ COMPLEX_FIELD (E_CONTACT_REV, ebb_m365_contact_get_rev, NULL),
+ STRING_FIELD (E_CONTACT_ASSISTANT, e_m365_contact_get_assistant_name,
e_m365_contact_add_assistant_name),
+ COMPLEX_FIELD (E_CONTACT_BIRTH_DATE, ebb_m365_contact_get_birthday,
ebb_m365_contact_add_birthday),
+ COMPLEX_FIELD (E_CONTACT_ADDRESS_WORK, ebb_m365_contact_get_address,
ebb_m365_contact_add_address),
+ STRING_FIELD (E_CONTACT_HOMEPAGE_URL, e_m365_contact_get_business_home_page,
e_m365_contact_add_business_home_page),
+ COMPLEX_FIELD (E_CONTACT_PHONE_BUSINESS, ebb_m365_contact_get_phone,
ebb_m365_contact_add_phone),
+ COMPLEX_FIELD (E_CONTACT_CATEGORIES, ebb_m365_contact_get_categories,
ebb_m365_contact_add_categories),
+ STRING_FIELD (E_CONTACT_ORG, e_m365_contact_get_company_name,
e_m365_contact_add_company_name),
+ STRING_FIELD (E_CONTACT_ORG_UNIT, e_m365_contact_get_department,
e_m365_contact_add_department),
+ COMPLEX_FIELD (E_CONTACT_EMAIL, ebb_m365_contact_get_emails,
ebb_m365_contact_add_emails),
+ COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_generation,
ebb_m365_contact_add_generation),
+ STRING_FIELD (E_CONTACT_GIVEN_NAME, e_m365_contact_get_given_name,
e_m365_contact_add_given_name),
+ COMPLEX_FIELD (E_CONTACT_ADDRESS_HOME, ebb_m365_contact_get_address,
ebb_m365_contact_add_address),
+ COMPLEX_FIELD (E_CONTACT_PHONE_HOME, ebb_m365_contact_get_phone,
ebb_m365_contact_add_phone),
+ COMPLEX_FIELD (E_CONTACT_IM_MSN, ebb_m365_contact_get_im_addresses,
ebb_m365_contact_add_im_addresses),
+ /* STRING_FIELD (???, e_m365_contact_get_initials,
e_m365_contact_add_initials), */
+ STRING_FIELD (E_CONTACT_TITLE, e_m365_contact_get_job_title,
e_m365_contact_add_job_title),
+ STRING_FIELD (E_CONTACT_MANAGER, e_m365_contact_get_manager,
e_m365_contact_add_manager),
+ COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_middle_name,
ebb_m365_contact_add_middle_name),
+ STRING_FIELD (E_CONTACT_PHONE_MOBILE, e_m365_contact_get_mobile_phone,
e_m365_contact_add_mobile_phone),
+ STRING_FIELD (E_CONTACT_NICKNAME, e_m365_contact_get_nick_name,
e_m365_contact_add_nick_name),
+ STRING_FIELD (E_CONTACT_OFFICE, e_m365_contact_get_office_location,
e_m365_contact_add_office_location),
+ COMPLEX_FIELD (E_CONTACT_ADDRESS_OTHER, ebb_m365_contact_get_address,
ebb_m365_contact_add_address),
+ STRING_FIELD (E_CONTACT_NOTE, e_m365_contact_get_personal_notes,
e_m365_contact_add_personal_notes),
+ STRING_FIELD (E_CONTACT_ROLE, e_m365_contact_get_profession,
e_m365_contact_add_profession),
+ STRING_FIELD (E_CONTACT_SPOUSE, e_m365_contact_get_spouse_name,
e_m365_contact_add_spouse_name),
+ STRING_FIELD (E_CONTACT_FAMILY_NAME, e_m365_contact_get_surname,
e_m365_contact_add_surname),
+ COMPLEX_FIELD (E_CONTACT_NAME, ebb_m365_contact_get_title,
ebb_m365_contact_add_title),
+ /* STRING_FIELD (???, e_m365_contact_get_yomi_company_name,
e_m365_contact_add_yomi_company_name), */
+ /* STRING_FIELD (???, e_m365_contact_get_yomi_given_name,
e_m365_contact_add_yomi_given_name), */
+ /* STRING_FIELD (???, e_m365_contact_get_yomi_surname,
e_m365_contact_add_yomi_surname), */
+ COMPLEX_ADDFN (E_CONTACT_FILE_AS, e_m365_contact_get_file_as,
ebb_m365_contact_add_file_as),
+ COMPLEX_FIELD_2 (E_CONTACT_PHOTO, ebb_m365_contact_get_photo,
ebb_m365_contact_add_photo)
+};
+
+static EContact *
+ebb_m365_json_contact_to_vcard (EBookBackendM365 *bbm365,
+ EM365Contact *m365_contact,
+ EM365Connection *cnc,
+ gchar **out_object,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EContact *contact;
+ gint ii;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (m365_contact != NULL, NULL);
+ g_return_val_if_fail (out_object != NULL, NULL);
+
+ *out_object = NULL;
+
+ contact = e_contact_new ();
+
+ for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+ if (mappings[ii].m365_get_func) {
+ ebb_m365_contact_get_string_attribute (m365_contact, contact, mappings[ii].field_id,
mappings[ii].m365_get_func);
+ } else if (mappings[ii].get_func) {
+ success = mappings[ii].get_func (bbm365, m365_contact, contact,
mappings[ii].field_id, cnc, cancellable, error);
+ }
+ }
+
+ if (success)
+ *out_object = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+ else
+ g_clear_object (&contact);
+
+ return contact;
+}
+
+static JsonBuilder *
+ebb_m365_contact_to_json_locked (EBookBackendM365 *bbm365,
+ EContact *new_contact,
+ EContact *old_contact, /* nullable */
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonBuilder *builder;
+ gint ii;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (new_contact != NULL, NULL);
+
+ builder = json_builder_new_immutable ();
+ e_m365_json_begin_object_member (builder, NULL);
+
+ for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+ if (mappings[ii].m365_add_func) {
+ ebb_m365_contact_add_string_attribute (new_contact, old_contact,
mappings[ii].field_id, builder, mappings[ii].m365_add_func);
+ } else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
+ success = mappings[ii].add_func (bbm365, new_contact, old_contact,
mappings[ii].field_id, NULL, builder, cancellable, error);
+ }
+ }
+
+ e_m365_json_end_object_member (builder);
+
+ if (!success)
+ g_clear_object (&builder);
+
+ return builder;
+}
+
+static gboolean
+ebb_m365_contact_to_json_2nd_go_locked (EBookBackendM365 *bbm365,
+ EContact *new_contact,
+ EContact *old_contact, /* nullable */
+ const gchar *m365_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gint ii;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (new_contact != NULL, FALSE);
+
+ for (ii = 0; success && ii < G_N_ELEMENTS (mappings); ii++) {
+ if (mappings[ii].add_in_second_go && mappings[ii].add_func) {
+ success = mappings[ii].add_func (bbm365, new_contact, old_contact,
mappings[ii].field_id, m365_id, NULL, cancellable, error);
+ }
+ }
+
+ return success;
+}
+
+static void
+ebb_m365_convert_error_to_client_error (GError **perror)
+{
+ GError *error = NULL;
+
+ if (!perror || !*perror || (*perror)->domain == E_CLIENT_ERROR || (*perror)->domain ==
E_BOOK_CLIENT_ERROR)
+ return;
+
+ /*if ((*perror)->domain == EWS_CONNECTION_ERROR) {
+ switch ((*perror)->code) {
+ case EWS_CONNECTION_ERROR_AUTHENTICATION_FAILED:
+ error = EC_ERROR_EX (E_CLIENT_ERROR_AUTHENTICATION_FAILED, (*perror)->message);
+ break;
+ case EWS_CONNECTION_ERROR_FOLDERNOTFOUND:
+ case EWS_CONNECTION_ERROR_MANAGEDFOLDERNOTFOUND:
+ case EWS_CONNECTION_ERROR_PARENTFOLDERNOTFOUND:
+ case EWS_CONNECTION_ERROR_PUBLICFOLDERSERVERNOTFOUND:
+ error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_NO_SUCH_BOOK, (*perror)->message);
+ break;
+ case EWS_CONNECTION_ERROR_EVENTNOTFOUND:
+ case EWS_CONNECTION_ERROR_ITEMNOTFOUND:
+ error = EBC_ERROR_EX (E_BOOK_CLIENT_ERROR_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)
+ error = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, (*perror)->message);
+ }*/
+
+ if (error) {
+ g_error_free (*perror);
+ *perror = error;
+ }
+}
+
+static void
+ebb_m365_maybe_disconnect_sync (EBookBackendM365 *bbm365,
+ GError **in_perror,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365));
+
+ if (in_perror && g_error_matches (*in_perror, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) {
+ e_book_meta_backend_disconnect_sync (E_BOOK_META_BACKEND (bbm365), cancellable, NULL);
+ e_backend_schedule_credentials_required (E_BACKEND (bbm365),
E_SOURCE_CREDENTIALS_REASON_REJECTED, NULL, 0, NULL, NULL, G_STRFUNC);
+ }
+}
+
+static gboolean
+ebb_m365_unset_connection_sync (EBookBackendM365 *bbm365,
+ gboolean is_disconnect,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (bbm365), FALSE);
+
+ LOCK (bbm365);
+
+ if (bbm365->priv->cnc) {
+ if (is_disconnect)
+ success = e_m365_connection_disconnect_sync (bbm365->priv->cnc, cancellable, error);
+ }
+
+ g_clear_object (&bbm365->priv->cnc);
+ g_clear_pointer (&bbm365->priv->folder_id, g_free);
+
+ UNLOCK (bbm365);
+
+ return success;
+}
+
+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)
+{
+ EBookBackendM365 *bbm365;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (out_auth_result != NULL, FALSE);
+
+ bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
+
+ LOCK (bbm365);
+
+ if (bbm365->priv->cnc) {
+ UNLOCK (bbm365);
+
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+
+ return TRUE;
+ } else {
+ EBackend *backend;
+ ESourceRegistry *registry;
+ ESource *source;
+ EM365Connection *cnc = NULL;
+ ESourceM365Folder *m365_folder_extension;
+ CamelM365Settings *m365_settings;
+ gchar *folder_id;
+
+ backend = E_BACKEND (bbm365);
+ source = e_backend_get_source (backend);
+ registry = e_book_backend_get_registry (E_BOOK_BACKEND (bbm365));
+ m365_settings = camel_m365_settings_get_from_backend (backend, registry);
+ g_warn_if_fail (m365_settings != NULL);
+
+ m365_folder_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
+ folder_id = e_source_m365_folder_dup_id (m365_folder_extension);
+
+ if (folder_id) {
+ cnc = e_m365_connection_new_for_backend (backend, registry, source, m365_settings);
+
+ *out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL,
E_M365_FOLDER_KIND_CONTACTS, NULL, folder_id,
+ out_certificate_pem, out_certificate_errors, cancellable, error);
+
+ if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+ bbm365->priv->cnc = g_object_ref (cnc);
+
+ g_warn_if_fail (bbm365->priv->folder_id == NULL);
+
+ g_free (bbm365->priv->folder_id);
+ bbm365->priv->folder_id = folder_id;
+
+ folder_id = NULL;
+ success = TRUE;
+
+ e_book_backend_set_writable (E_BOOK_BACKEND (bbm365), TRUE);
+ }
+ } else {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Folder ID is
not set")));
+ }
+
+ g_clear_object (&cnc);
+ g_free (folder_id);
+ }
+
+ UNLOCK (bbm365);
+
+ ebb_m365_convert_error_to_client_error (error);
+
+ return success;
+}
+
+static gboolean
+ebb_m365_disconnect_sync (EBookMetaBackend *meta_backend,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+
+ return ebb_m365_unset_connection_sync (E_BOOK_BACKEND_M365 (meta_backend), TRUE, cancellable, error);
+}
+
+typedef struct _ObjectsDeltaData {
+ EBookBackendM365 *bbm365;
+ ECache *cache;
+ GSList **out_created_objects;
+ GSList **out_modified_objects;
+ GSList **out_removed_objects;
+} ObjectsDeltaData;
+
+static gboolean
+ebb_m365_get_objects_delta_cb (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned objects from the server
*/
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ObjectsDeltaData *odd = user_data;
+ GSList *link;
+
+ g_return_val_if_fail (odd != NULL, FALSE);
+
+ for (link = (GSList *) results; link && !g_cancellable_is_cancelled (cancellable); link =
g_slist_next (link)) {
+ EM365Contact *contact = link->data;
+ const gchar *id;
+
+ if (!contact)
+ continue;
+
+ id = e_m365_contact_get_id (contact);
+
+ if (!id)
+ continue;
+
+ if (e_m365_delta_is_removed_object (contact)) {
+ *(odd->out_removed_objects) = g_slist_prepend (*(odd->out_removed_objects),
+ e_book_meta_backend_info_new (id, NULL, NULL, NULL));
+ } else {
+ GSList **out_slist;
+ EContact *vcard;
+ gchar *object;
+
+ if (e_cache_contains (odd->cache, id, E_CACHE_INCLUDE_DELETED))
+ out_slist = odd->out_modified_objects;
+ else
+ out_slist = odd->out_created_objects;
+
+ vcard = ebb_m365_json_contact_to_vcard (odd->bbm365, contact, cnc, &object,
cancellable, error);
+
+ g_clear_object (&vcard);
+
+ if (!g_cancellable_is_cancelled (cancellable))
+ g_warn_if_fail (object != NULL);
+
+ if (object) {
+ EBookMetaBackendInfo *nfo;
+
+ nfo = e_book_meta_backend_info_new (id,
+ e_m365_contact_get_change_key (contact),
+ object, NULL);
+
+ nfo->extra = object; /* assumes ownership, to avoid unnecessary re-allocation
*/
+
+ *out_slist = g_slist_prepend (*out_slist, nfo);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+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)
+{
+ EBookBackendM365 *bbm365;
+ EBookCache *book_cache;
+ ObjectsDeltaData odd;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (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_modified_objects != NULL, FALSE);
+ g_return_val_if_fail (out_removed_objects != NULL, FALSE);
+
+ *out_created_objects = NULL;
+ *out_modified_objects = NULL;
+ *out_removed_objects = NULL;
+
+ bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
+
+ book_cache = e_book_meta_backend_ref_cache (meta_backend);
+ g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
+
+ odd.bbm365 = bbm365;
+ odd.cache = E_CACHE (book_cache);
+ odd.out_created_objects = out_created_objects;
+ odd.out_modified_objects = out_modified_objects;
+ odd.out_removed_objects = out_removed_objects;
+
+ LOCK (bbm365);
+
+ success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
+ E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, last_sync_tag, 0,
+ ebb_m365_get_objects_delta_cb, &odd,
+ out_new_sync_tag, cancellable, &local_error);
+
+ if (e_m365_connection_util_delta_token_failed (local_error)) {
+ GSList *known_uids = NULL, *link;
+
+ g_clear_error (&local_error);
+
+ if (e_book_cache_search_uids (book_cache, NULL, &known_uids, cancellable, error)) {
+ for (link = known_uids; link; link = g_slist_next (link)) {
+ const gchar *uid = link->data;
+
+ if (uid) {
+ *out_removed_objects = g_slist_prepend (*out_removed_objects,
+ e_book_meta_backend_info_new (uid, NULL, NULL, NULL));
+ }
+ }
+ }
+
+ e_cache_remove_all (E_CACHE (book_cache), cancellable, NULL);
+
+ g_slist_free_full (known_uids, g_free);
+
+ success = e_m365_connection_get_objects_delta_sync (bbm365->priv->cnc, NULL,
+ E_M365_FOLDER_KIND_CONTACTS, bbm365->priv->folder_id, NULL, NULL, 0,
+ ebb_m365_get_objects_delta_cb, &odd,
+ out_new_sync_tag, cancellable, &local_error);
+ } else if (local_error) {
+ g_propagate_error (error, local_error);
+ }
+
+ UNLOCK (bbm365);
+
+ ebb_m365_convert_error_to_client_error (error);
+ ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
+
+ g_clear_object (&book_cache);
+
+ return success;
+}
+
+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)
+{
+ EBookBackendM365 *bbm365;
+ EM365Contact *contact = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_contact, FALSE);
+ g_return_val_if_fail (out_extra != NULL, FALSE);
+
+ bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
+
+ LOCK (bbm365);
+
+ success = e_m365_connection_get_contact_sync (bbm365->priv->cnc, NULL,
+ bbm365->priv->folder_id, uid, &contact, cancellable, error);
+
+ if (success) {
+ *out_contact = ebb_m365_json_contact_to_vcard (bbm365, contact, bbm365->priv->cnc, out_extra,
cancellable, error);
+
+ if (contact)
+ json_object_unref (contact);
+ }
+
+ UNLOCK (bbm365);
+
+ ebb_m365_convert_error_to_client_error (error);
+ ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
+
+ return success;
+}
+
+static gboolean
+ebb_m365_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)
+{
+ EBookBackendM365 *bbm365;
+ EContact *tmp_contact = NULL, *old_contact = NULL;
+ JsonBuilder *builder;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (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);
+
+ if (GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST))) {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED, _("Cannot save contact
list into a Microsoft 365 address book")));
+ return FALSE;
+ }
+
+ bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
+
+ LOCK (bbm365);
+
+ if (e_vcard_get_attribute (E_VCARD (contact), EVC_PHOTO)) {
+ tmp_contact = e_contact_duplicate (contact);
+ contact = tmp_contact;
+
+ e_contact_inline_local_photos (contact, NULL);
+ }
+
+ if (extra && *extra)
+ old_contact = e_contact_new_from_vcard (extra);
+
+ builder = ebb_m365_contact_to_json_locked (bbm365, contact, old_contact, cancellable, error);
+
+ if (builder) {
+ if (overwrite_existing) {
+ const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
+
+ success = e_m365_connection_update_contact_sync (bbm365->priv->cnc, NULL,
bbm365->priv->folder_id,
+ uid, builder, cancellable, error);
+
+ if (success)
+ success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact,
old_contact, uid, cancellable, error);
+
+ if (success)
+ *out_new_extra = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+ } else {
+ EM365Contact *created_contact = NULL;
+
+ success = e_m365_connection_create_contact_sync (bbm365->priv->cnc, NULL,
bbm365->priv->folder_id,
+ builder, &created_contact, cancellable, error);
+
+ if (success && created_contact) {
+ const gchar *m365_id = e_m365_contact_get_id (created_contact);
+
+ success = ebb_m365_contact_to_json_2nd_go_locked (bbm365, contact,
old_contact, m365_id, cancellable, error);
+ }
+
+ if (success && created_contact) {
+ EContact *vcard;
+
+ *out_new_uid = g_strdup (e_m365_contact_get_id (created_contact));
+
+ vcard = ebb_m365_json_contact_to_vcard (bbm365, created_contact,
bbm365->priv->cnc, out_new_extra, cancellable, error);
+
+ if (!vcard)
+ success = FALSE;
+
+ g_clear_object (&vcard);
+ }
+
+ if (created_contact)
+ json_object_unref (created_contact);
+ }
+
+ g_clear_object (&builder);
+ }
+
+ UNLOCK (bbm365);
+
+ ebb_m365_convert_error_to_client_error (error);
+ ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
+
+ g_clear_object (&old_contact);
+ g_clear_object (&tmp_contact);
+
+ return success;
+}
+
+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)
+{
+ EBookBackendM365 *bbm365;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+
+ bbm365 = E_BOOK_BACKEND_M365 (meta_backend);
+
+ LOCK (bbm365);
+
+ success = e_m365_connection_delete_contact_sync (bbm365->priv->cnc, NULL,
+ bbm365->priv->folder_id, uid, cancellable, error);
+
+ UNLOCK (bbm365);
+
+ ebb_m365_convert_error_to_client_error (error);
+ ebb_m365_maybe_disconnect_sync (bbm365, error, cancellable);
+
+ return success;
+}
+
+static gboolean
+ebb_m365_search_sync (EBookMetaBackend *meta_backend,
+ const gchar *expr,
+ gboolean meta_contact,
+ GSList **out_contacts,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+
+ /* Ignore errors, just try its best */
+ /*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable,
NULL);*/
+
+ /* Chain up to parent's method */
+ return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_sync (meta_backend, expr,
meta_contact, out_contacts, cancellable, error);
+}
+
+static gboolean
+ebb_m365_search_uids_sync (EBookMetaBackend *meta_backend,
+ const gchar *expr,
+ GSList **out_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (meta_backend), FALSE);
+
+ /* Ignore errors, just try its best */
+ /*ebb_m365_update_cache_for_expression (E_BOOK_BACKEND_M365 (meta_backend), expr, cancellable,
NULL);*/
+
+ /* Chain up to parent's method */
+ return E_BOOK_META_BACKEND_CLASS (e_book_backend_m365_parent_class)->search_uids_sync (meta_backend,
expr,
+ out_uids, cancellable, error);
+}
+
+static gchar *
+ebb_m365_get_backend_property (EBookBackend *book_backend,
+ const gchar *prop_name)
+{
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_M365 (book_backend), NULL);
+ g_return_val_if_fail (prop_name != NULL, NULL);
+
+ if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+ return g_strjoin (",",
+ "net",
+ "contact-lists",
+ "do-initial-query",
+ e_book_meta_backend_get_capabilities (E_BOOK_META_BACKEND (book_backend)),
+ NULL);
+ } 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 *buffer;
+ gchar *fields;
+ gint ii;
+
+ buffer = g_string_sized_new (1024);
+
+ for (ii = 0; ii < G_N_ELEMENTS (mappings); ii++) {
+ if (buffer->len > 0)
+ g_string_append_c (buffer, ',');
+
+ g_string_append (buffer, e_contact_field_name (mappings[ii].field_id));
+ }
+
+ fields = g_strjoin (
+ ",",
+ buffer->str,
+ e_contact_field_name (E_CONTACT_FULL_NAME),
+ e_contact_field_name (E_CONTACT_EMAIL_1),
+ e_contact_field_name (E_CONTACT_EMAIL_2),
+ e_contact_field_name (E_CONTACT_EMAIL_3),
+ e_contact_field_name (E_CONTACT_EMAIL_4),
+ NULL);
+
+ g_string_free (buffer, TRUE);
+
+ return fields;
+ }
+
+ /* Chain up to parent's method. */
+ return E_BOOK_BACKEND_CLASS (e_book_backend_m365_parent_class)->impl_get_backend_property
(book_backend, prop_name);
+}
+
+static gboolean
+ebb_m365_get_destination_address (EBackend *backend,
+ gchar **host,
+ guint16 *port)
+{
+ 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;
+
+ *host = g_strdup ("graph.microsoft.com");
+ *port = 443;
+
+ return TRUE;
+}
+
+static void
+e_book_backend_m365_dispose (GObject *object)
+{
+ EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
+
+ ebb_m365_unset_connection_sync (bbm365, FALSE, NULL, NULL);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_m365_parent_class)->dispose (object);
+}
+
+static void
+e_book_backend_m365_finalize (GObject *object)
+{
+ EBookBackendM365 *bbm365 = E_BOOK_BACKEND_M365 (object);
+
+ g_rec_mutex_clear (&bbm365->priv->property_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_book_backend_m365_parent_class)->finalize (object);
+}
+
+static void
+e_book_backend_m365_init (EBookBackendM365 *bbm365)
+{
+ bbm365->priv = e_book_backend_m365_get_instance_private (bbm365);
+
+ g_rec_mutex_init (&bbm365->priv->property_lock);
+}
+
+static void
+e_book_backend_m365_class_init (EBookBackendM365Class *klass)
+{
+ GObjectClass *object_class;
+ EBackendClass *backend_class;
+ EBookBackendClass *book_backend_class;
+ EBookMetaBackendClass *book_meta_backend_class;
+
+ book_meta_backend_class = E_BOOK_META_BACKEND_CLASS (klass);
+ book_meta_backend_class->backend_module_filename = "libebookbackendmicrosoft365.so";
+ book_meta_backend_class->backend_factory_type_name = "EBookBackendM365Factory";
+ book_meta_backend_class->connect_sync = ebb_m365_connect_sync;
+ book_meta_backend_class->disconnect_sync = ebb_m365_disconnect_sync;
+ book_meta_backend_class->get_changes_sync = ebb_m365_get_changes_sync;
+ book_meta_backend_class->load_contact_sync = ebb_m365_load_contact_sync;
+ book_meta_backend_class->save_contact_sync = ebb_m365_save_contact_sync;
+ book_meta_backend_class->remove_contact_sync = ebb_m365_remove_contact_sync;
+ book_meta_backend_class->search_sync = ebb_m365_search_sync;
+ book_meta_backend_class->search_uids_sync = ebb_m365_search_uids_sync;
+
+ book_backend_class = E_BOOK_BACKEND_CLASS (klass);
+ book_backend_class->impl_get_backend_property = ebb_m365_get_backend_property;
+
+ backend_class = E_BACKEND_CLASS (klass);
+ backend_class->get_destination_address = ebb_m365_get_destination_address;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = e_book_backend_m365_dispose;
+ object_class->finalize = e_book_backend_m365_finalize;
+}
diff --git a/src/Microsoft365/addressbook/e-book-backend-m365.h
b/src/Microsoft365/addressbook/e-book-backend-m365.h
new file mode 100644
index 00000000..4639648b
--- /dev/null
+++ b/src/Microsoft365/addressbook/e-book-backend-m365.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_BOOK_BACKEND_M365_H
+#define E_BOOK_BACKEND_M365_H
+
+#include <libedata-book/libedata-book.h>
+
+#define E_TYPE_BOOK_BACKEND_M365 (e_book_backend_m365_get_type ())
+#define E_BOOK_BACKEND_M365(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_BOOK_BACKEND_M365,
EBookBackendM365))
+#define E_BOOK_BACKEND_M365_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_BOOK_BACKEND_M365,
EBookBackendM365Class))
+#define E_IS_BOOK_BACKEND_M365(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_BOOK_BACKEND_M365))
+#define E_IS_BOOK_BACKEND_M365_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_BOOK_BACKEND_M365))
+#define E_BOOK_BACKEND_M365_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_BOOK_BACKEND_M365,
EBookBackenM365Class))
+
+typedef struct _EBookBackendM365Private EBookBackendM365Private;
+
+typedef struct {
+ EBookMetaBackend parent_object;
+ EBookBackendM365Private *priv;
+} EBookBackendM365;
+
+typedef struct {
+ EBookMetaBackendClass parent_class;
+} EBookBackendM365Class;
+
+GType e_book_backend_m365_get_type (void);
+
+#endif /* E_BOOK_BACKEND_M365_H */
diff --git a/src/Microsoft365/calendar/CMakeLists.txt b/src/Microsoft365/calendar/CMakeLists.txt
new file mode 100644
index 00000000..56f36cee
--- /dev/null
+++ b/src/Microsoft365/calendar/CMakeLists.txt
@@ -0,0 +1,61 @@
+set(DEPENDENCIES
+ evolution-microsoft365
+)
+
+set(SOURCES
+ e-cal-backend-m365.c
+ e-cal-backend-m365.h
+ e-cal-backend-m365-factory.c
+)
+
+add_library(ecalbackendmicrosoft365 MODULE
+ ${SOURCES}
+)
+
+add_dependencies(ecalbackendmicrosoft365
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(ecalbackendmicrosoft365 PRIVATE
+ -DG_LOG_DOMAIN=\"ecalbackendmicrosoft365\"
+ -DM365_DATADIR=\"${ewsdatadir}\"
+ -DM365_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+ -DM365_SRCDIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"
+)
+
+target_compile_options(ecalbackendmicrosoft365 PUBLIC
+ ${CAMEL_CFLAGS}
+ ${EVOLUTION_CALENDAR_CFLAGS}
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBECAL_CFLAGS}
+ ${LIBEDATACAL_CFLAGS}
+ ${SOUP_CFLAGS}
+)
+
+target_include_directories(ecalbackendmicrosoft365 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/src/Microsoft365
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${EVOLUTION_CALENDAR_INCLUDE_DIRS}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBECAL_INCLUDE_DIRS}
+ ${LIBEDATACAL_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+)
+
+target_link_libraries(ecalbackendmicrosoft365
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+ ${EVOLUTION_CALENDAR_LDFLAGS}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBECAL_LDFLAGS}
+ ${LIBEDATACAL_LDFLAGS}
+ ${SOUP_LDFLAGS}
+)
+
+install(TARGETS ecalbackendmicrosoft365
+ DESTINATION ${ecal_backenddir}
+)
diff --git a/src/Microsoft365/calendar/e-cal-backend-m365-factory.c
b/src/Microsoft365/calendar/e-cal-backend-m365-factory.c
new file mode 100644
index 00000000..1d29ad38
--- /dev/null
+++ b/src/Microsoft365/calendar/e-cal-backend-m365-factory.c
@@ -0,0 +1,139 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedata-cal/libedata-cal.h>
+
+#include "common/e-oauth2-service-microsoft365.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-cal-backend-m365.h"
+
+#define FACTORY_NAME "microsoft365"
+
+typedef ECalBackendFactory ECalBackendM365EventsFactory;
+typedef ECalBackendFactoryClass ECalBackendM365EventsFactoryClass;
+
+typedef ECalBackendFactory ECalBackendM365JournalFactory;
+typedef ECalBackendFactoryClass ECalBackendM365JournalFactoryClass;
+
+typedef ECalBackendFactory ECalBackendM365TodosFactory;
+typedef ECalBackendFactoryClass ECalBackendM365TodosFactoryClass;
+
+static EModule *e_module;
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_cal_backend_m365_events_factory_get_type (void);
+GType e_cal_backend_m365_journal_factory_get_type (void);
+GType e_cal_backend_m365_todos_factory_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (ECalBackendM365EventsFactory, e_cal_backend_m365_events_factory,
E_TYPE_CAL_BACKEND_FACTORY)
+G_DEFINE_DYNAMIC_TYPE (ECalBackendM365JournalFactory, e_cal_backend_m365_journal_factory,
E_TYPE_CAL_BACKEND_FACTORY)
+G_DEFINE_DYNAMIC_TYPE (ECalBackendM365TodosFactory, e_cal_backend_m365_todos_factory,
E_TYPE_CAL_BACKEND_FACTORY)
+
+static void
+e_cal_backend_m365_events_factory_class_init (ECalBackendFactoryClass *class)
+{
+ EBackendFactoryClass *backend_factory_class;
+
+ backend_factory_class = E_BACKEND_FACTORY_CLASS (class);
+ backend_factory_class->e_module = e_module;
+ backend_factory_class->share_subprocess = TRUE;
+
+ class->factory_name = FACTORY_NAME;
+ class->component_kind = I_CAL_VEVENT_COMPONENT;
+ class->backend_type = E_TYPE_CAL_BACKEND_M365;
+}
+
+static void
+e_cal_backend_m365_events_factory_class_finalize (ECalBackendFactoryClass *class)
+{
+}
+
+static void
+e_cal_backend_m365_events_factory_init (ECalBackendFactory *factory)
+{
+}
+
+static void
+e_cal_backend_m365_journal_factory_class_init (ECalBackendFactoryClass *class)
+{
+ EBackendFactoryClass *backend_factory_class;
+
+ backend_factory_class = E_BACKEND_FACTORY_CLASS (class);
+ backend_factory_class->e_module = e_module;
+ backend_factory_class->share_subprocess = TRUE;
+
+ class->factory_name = FACTORY_NAME;
+ class->component_kind = I_CAL_VJOURNAL_COMPONENT;
+ class->backend_type = E_TYPE_CAL_BACKEND_M365;
+}
+
+static void
+e_cal_backend_m365_journal_factory_class_finalize (ECalBackendFactoryClass *class)
+{
+}
+
+static void
+e_cal_backend_m365_journal_factory_init (ECalBackendFactory *factory)
+{
+}
+
+static void
+e_cal_backend_m365_todos_factory_class_init (ECalBackendFactoryClass *class)
+{
+ EBackendFactoryClass *backend_factory_class;
+
+ backend_factory_class = E_BACKEND_FACTORY_CLASS (class);
+ backend_factory_class->e_module = e_module;
+ backend_factory_class->share_subprocess = TRUE;
+
+ class->factory_name = FACTORY_NAME;
+ class->component_kind = I_CAL_VTODO_COMPONENT;
+ class->backend_type = E_TYPE_CAL_BACKEND_M365;
+}
+
+static void
+e_cal_backend_m365_todos_factory_class_finalize (ECalBackendFactoryClass *class)
+{
+}
+
+static void
+e_cal_backend_m365_todos_factory_init (ECalBackendFactory *factory)
+{
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, M365_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ e_module = E_MODULE (type_module);
+
+ e_oauth2_service_microsoft365_type_register (type_module);
+ e_source_m365_folder_type_register (type_module);
+
+ if (g_strcmp0 (g_getenv ("ENABLE_M365"), "1") == 0) {
+ e_cal_backend_m365_events_factory_register_type (type_module);
+ e_cal_backend_m365_journal_factory_register_type (type_module);
+ e_cal_backend_m365_todos_factory_register_type (type_module);
+ }
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+ e_module = NULL;
+}
diff --git a/src/Microsoft365/calendar/e-cal-backend-m365.c b/src/Microsoft365/calendar/e-cal-backend-m365.c
new file mode 100644
index 00000000..6f125dfc
--- /dev/null
+++ b/src/Microsoft365/calendar/e-cal-backend-m365.c
@@ -0,0 +1,3710 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedata-cal/libedata-cal.h>
+#include <libecal/libecal.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+#include "common/e-m365-tz-utils.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-cal-backend-m365.h"
+
+#define EC_ERROR(_code) e_client_error_create (_code, NULL)
+#define EC_ERROR_EX(_code, _msg) e_client_error_create (_code, _msg)
+#define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
+#define ECC_ERROR_EX(_code, _msg) e_cal_client_error_create (_code, _msg)
+
+#define LOCK(_cb) g_rec_mutex_lock (&_cb->priv->property_lock)
+#define UNLOCK(_cb) g_rec_mutex_unlock (&_cb->priv->property_lock)
+
+struct _ECalBackendM365Private {
+ GRecMutex property_lock;
+ EM365Connection *cnc;
+ gchar *group_id;
+ gchar *folder_id;
+ gchar *attachments_dir;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ECalBackendM365, e_cal_backend_m365, E_TYPE_CAL_META_BACKEND)
+
+static void
+ecb_m365_convert_error_to_client_error (GError **perror)
+{
+ GError *error = NULL;
+
+ if (!perror || !*perror ||
+ (*perror)->domain == E_CLIENT_ERROR ||
+ (*perror)->domain == E_CAL_CLIENT_ERROR)
+ return;
+
+ /*if ((*perror)->domain == M365_CONNECTION_ERROR) {
+ switch ((*perror)->code) {
+ case M365_CONNECTION_ERROR_AUTHENTICATION_FAILED:
+ error = EC_ERROR_EX (E_CLIENT_ERROR_AUTHENTICATION_FAILED, (*perror)->message);
+ break;
+ case M365_CONNECTION_ERROR_FOLDERNOTFOUND:
+ case M365_CONNECTION_ERROR_MANAGEDFOLDERNOTFOUND:
+ case M365_CONNECTION_ERROR_PARENTFOLDERNOTFOUND:
+ case M365_CONNECTION_ERROR_PUBLICFOLDERSERVERNOTFOUND:
+ error = ECC_ERROR_EX (E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR, (*perror)->message);
+ break;
+ case M365_CONNECTION_ERROR_EVENTNOTFOUND:
+ case M365_CONNECTION_ERROR_ITEMNOTFOUND:
+ error = ECC_ERROR_EX (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND, (*perror)->message);
+ break;
+ case M365_CONNECTION_ERROR_UNAVAILABLE:
+ g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
(*perror)->message);
+ break;
+ }
+
+ if (!error)
+ error = EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, (*perror)->message);
+ }*/
+
+ if (error) {
+ g_error_free (*perror);
+ *perror = error;
+ }
+}
+
+static const gchar *
+ecb_m365_get_component_from_extra (const gchar *extra)
+{
+ gchar *enter;
+
+ if (!extra)
+ return NULL;
+
+ enter = strchr (extra, '\n');
+
+ if (!enter)
+ return NULL;
+
+ return enter + 1;
+}
+
+/* Modifies inout_extra, cannot be called multiple times with the same arguments */
+static void
+ecb_m365_split_extra (gchar *inout_extra,
+ const gchar **out_change_key,
+ const gchar **out_ical_comp)
+{
+ gchar *enter;
+
+ if (!inout_extra)
+ return;
+
+ enter = (gchar *) ecb_m365_get_component_from_extra (inout_extra);
+ g_return_if_fail (enter != NULL);
+
+ enter[-1] = '\0';
+
+ if (out_change_key)
+ *out_change_key = inout_extra;
+
+ if (out_ical_comp)
+ *out_ical_comp = enter;
+}
+
+static void
+ecb_m365_get_uid (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ const gchar *id;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ id = e_m365_event_get_id (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ id = e_m365_task_get_id (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ i_cal_component_set_uid (inout_comp, id);
+}
+
+static void
+ecb_m365_get_date_time (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ time_t tt = (time_t) 0;
+
+ if (prop_kind == I_CAL_CREATED_PROPERTY) {
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ tt = e_m365_event_get_created_date_time (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ tt = e_m365_task_get_created_date_time (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ } else if (prop_kind == I_CAL_LASTMODIFIED_PROPERTY) {
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ tt = e_m365_event_get_last_modified_date_time (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ tt = e_m365_task_get_last_modified_date_time (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ } else {
+ g_warn_if_reached ();
+ }
+
+ if (tt > (time_t) 0) {
+ ICalProperty *prop;
+ ICalTime *itt;
+
+ itt = i_cal_time_new_from_timet_with_zone (tt, FALSE, i_cal_timezone_get_utc_timezone ());
+
+ if (prop_kind == I_CAL_CREATED_PROPERTY)
+ prop = i_cal_property_new_created (itt);
+ else /* I_CAL_LASTMODIFIED_PROPERTY */
+ prop = i_cal_property_new_lastmodified (itt);
+
+ i_cal_component_take_property (inout_comp, prop);
+
+ g_clear_object (&itt);
+ }
+}
+
+static ICalTimezone *
+ecb_m365_get_timezone_sync (ECalBackendM365 *cbm365,
+ const gchar *tzid)
+{
+ ICalTimezone *zone;
+ ECalCache *cal_cache;
+
+ if (!tzid)
+ return NULL;
+
+ cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cbm365));
+
+ if (!cal_cache)
+ return NULL;
+
+ zone = e_cal_cache_resolve_timezone_cb (tzid, cal_cache, NULL, NULL);
+
+ g_object_unref (cal_cache);
+
+ return zone;
+}
+
+static void
+ecb_m365_get_date_time_zone (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365DateTimeWithZone *value;
+ ICalTimezone *tz;
+ ICalTime *itt;
+ time_t tt;
+ const gchar *tzid, *zone;
+ gboolean is_date;
+
+ if (prop_kind == I_CAL_DTSTART_PROPERTY) {
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ value = e_m365_event_get_start (m365_object);
+ tzid = e_m365_event_get_original_start_timezone (m365_object);
+ is_date = e_m365_event_get_is_all_day (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ value = e_m365_task_get_start_date_time (m365_object);
+ tzid = "UTC";
+ is_date = TRUE;
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ } else if (prop_kind == I_CAL_DTEND_PROPERTY) {
+ value = e_m365_event_get_end (m365_object);
+ tzid = e_m365_event_get_original_end_timezone (m365_object);
+ is_date = e_m365_event_get_is_all_day (m365_object);
+ } else if (prop_kind == I_CAL_COMPLETED_PROPERTY) {
+ value = e_m365_task_get_completed_date_time (m365_object);
+ tzid = "UTC";
+ is_date = TRUE;
+ } else if (prop_kind == I_CAL_DUE_PROPERTY) {
+ value = e_m365_task_get_due_date_time (m365_object);
+ tzid = "UTC";
+ is_date = TRUE;
+ } else {
+ g_warn_if_reached ();
+ return;
+ }
+
+ if (!value)
+ return;
+
+ tt = e_m365_date_time_get_date_time (value);
+ zone = e_m365_date_time_get_time_zone (value);
+
+ if (zone && *zone)
+ zone = e_m365_tz_utils_get_ical_equivalent (zone);
+
+ tz = zone && *zone ? ecb_m365_get_timezone_sync (cbm365, zone) : NULL;
+
+ if (!tz)
+ tz = i_cal_timezone_get_utc_timezone ();
+
+ itt = i_cal_time_new_from_timet_with_zone (tt, is_date, tz);
+
+ tzid = e_m365_tz_utils_get_ical_equivalent (tzid);
+
+ if (!tzid)
+ tzid = "UTC";
+
+ tz = ecb_m365_get_timezone_sync (cbm365, tzid);
+
+ if (tz && !is_date)
+ i_cal_time_convert_to_zone_inplace (itt, tz);
+
+ if (prop_kind == I_CAL_DTSTART_PROPERTY)
+ i_cal_component_set_dtstart (inout_comp, itt);
+ else if (prop_kind == I_CAL_DTEND_PROPERTY)
+ i_cal_component_set_dtend (inout_comp, itt);
+ else if (prop_kind == I_CAL_COMPLETED_PROPERTY)
+ i_cal_component_take_property (inout_comp, i_cal_property_new_completed (itt));
+ else /* if (prop_kind == I_CAL_DUE_PROPERTY) */
+ i_cal_component_set_due (inout_comp, itt);
+
+ g_clear_object (&itt);
+}
+
+static void
+ecb_m365_add_date_time_zone (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ ICalProperty *new_prop;
+ ICalParameter *new_param;
+ ICalTime *old_value, *new_value;
+ const gchar *new_tzid = NULL;
+ void (* add_func) (JsonBuilder *builder, time_t date_time, const gchar *zone) = NULL;
+ gboolean same = FALSE;
+
+ if (prop_kind == I_CAL_DTSTART_PROPERTY) {
+ new_value = i_cal_component_get_dtstart (new_comp);
+ old_value = old_comp ? i_cal_component_get_dtstart (old_comp) : NULL;
+
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ add_func = e_m365_event_add_start;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ add_func = e_m365_task_add_start_date_time;
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ } else if (prop_kind == I_CAL_DTEND_PROPERTY) {
+ new_value = i_cal_component_get_dtend (new_comp);
+ old_value = old_comp ? i_cal_component_get_dtend (old_comp) : NULL;
+ add_func = e_m365_event_add_end;
+ } else if (prop_kind == I_CAL_COMPLETED_PROPERTY) {
+ ICalProperty *new_prop, *old_prop;
+
+ new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+ old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+ new_value = new_prop ? i_cal_property_get_completed (new_prop) : NULL;
+ old_value = old_prop ? i_cal_property_get_completed (old_prop) : NULL;
+ add_func = e_m365_task_add_completed_date_time;
+
+ g_clear_object (&new_prop);
+ g_clear_object (&old_prop);
+ } else if (prop_kind == I_CAL_DUE_PROPERTY) {
+ new_value = i_cal_component_get_due (new_comp);
+ old_value = old_comp ? i_cal_component_get_due (old_comp) : NULL;
+ add_func = e_m365_task_add_due_date_time;
+ } else {
+ g_warn_if_reached ();
+ return;
+ }
+
+ if (!new_value && !old_value)
+ return;
+
+ new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+ new_param = new_prop ? i_cal_property_get_first_parameter (new_prop, I_CAL_TZID_PARAMETER) : NULL;
+
+ if (new_param)
+ new_tzid = i_cal_parameter_get_tzid (new_param);
+
+ if (new_value && old_value) {
+ same = i_cal_time_compare (new_value, old_value) == 0;
+
+ if (same) {
+ ICalProperty *old_prop;
+ ICalParameter *old_param;
+ const gchar *old_tzid;
+
+ old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+ old_param = old_prop ? i_cal_property_get_first_parameter (old_prop,
I_CAL_TZID_PARAMETER) : NULL;
+ old_tzid = old_param ? i_cal_parameter_get_tzid (old_param) : NULL;
+
+ same = g_strcmp0 (old_tzid, new_tzid) == 0;
+
+ g_clear_object (&old_param);
+ g_clear_object (&old_prop);
+ }
+ }
+
+ if (!same) {
+ ICalTimezone *izone = NULL;
+ const gchar *wzone = NULL;
+ time_t tt;
+
+ if (new_tzid) {
+ izone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cbm365), new_tzid);
+
+ if (izone)
+ wzone = e_m365_tz_utils_get_msdn_equivalent (i_cal_timezone_get_location
(izone));
+ }
+
+ tt = i_cal_time_as_timet_with_zone (new_value, wzone ? NULL : izone);
+
+ add_func (builder, tt, wzone);
+ }
+
+ g_clear_object (&new_prop);
+ g_clear_object (&new_param);
+ g_clear_object (&new_value);
+ g_clear_object (&old_value);
+}
+
+static void
+ecb_m365_get_categories (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ JsonArray *categories;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ categories = e_m365_event_get_categories (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ categories = e_m365_task_get_categories (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ if (categories) {
+ GString *categories_str = NULL;
+ guint ii, len;
+
+ len = json_array_get_length (categories);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *category;
+
+ category = json_array_get_string_element (categories, ii);
+
+ if (category && *category) {
+ gchar *ical_str = i_cal_value_encode_ical_string (category);
+
+ if (ical_str && *ical_str) {
+ if (!categories_str) {
+ categories_str = g_string_new (ical_str);
+ } else {
+ g_string_append_c (categories_str, ',');
+ g_string_append (categories_str, ical_str);
+ }
+ }
+
+ g_free (ical_str);
+ }
+ }
+
+ if (categories_str) {
+ i_cal_component_take_property (inout_comp, i_cal_property_new_categories
(categories_str->str));
+
+ g_string_free (categories_str, TRUE);
+ }
+ }
+}
+
+static void
+ecb_m365_extract_categories (ICalComponent *comp,
+ GHashTable **out_hash, /* gchar * ~> NULL */
+ GSList **out_slist) /* gchar * */
+{
+ ICalProperty *prop;
+
+ if (!comp)
+ return;
+
+ for (prop = i_cal_component_get_first_property (comp, I_CAL_CATEGORIES_PROPERTY);
+ prop;
+ g_object_unref (prop), prop = i_cal_component_get_next_property (comp,
I_CAL_CATEGORIES_PROPERTY)) {
+ const gchar *categories;
+
+ categories = i_cal_property_get_categories (prop);
+
+ if (categories && *categories) {
+ if (out_hash && !*out_hash)
+ *out_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ if (strchr (categories, ',')) {
+ gchar **strv;
+ guint ii;
+
+ strv = g_strsplit (categories, ",", -1);
+
+ for (ii = 0; strv[ii]; ii++) {
+ gchar *category = g_strchomp (strv[ii]);
+
+ if (*category) {
+ if (out_hash) {
+ g_hash_table_insert (*out_hash, category, NULL);
+ } else if (out_slist) {
+ *out_slist = g_slist_prepend (*out_slist, category);
+ } else {
+ g_warn_if_reached ();
+ g_free (category);
+ }
+ } else {
+ g_free (category);
+ }
+ }
+
+ g_free (strv);
+ } else if (out_hash) {
+ g_hash_table_insert (*out_hash, g_strchomp (g_strdup (categories)), NULL);
+ } else if (out_slist) {
+ *out_slist = g_slist_prepend (*out_slist, g_strchomp (g_strdup (categories)));
+ } else {
+ g_warn_if_reached ();
+ }
+ }
+ }
+
+ g_clear_object (&prop);
+
+ if (out_slist && *out_slist)
+ *out_slist = g_slist_reverse (*out_slist);
+}
+
+static void
+ecb_m365_add_categories (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ GHashTable *old_value = NULL;
+ GSList *new_value = NULL;
+ void (* begin_categories_func) (JsonBuilder *builder);
+ void (* end_categories_func) (JsonBuilder *builder);
+ void (* add_category_func) (JsonBuilder *builder, const gchar *category);
+
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ begin_categories_func = e_m365_event_begin_categories;
+ end_categories_func = e_m365_event_end_categories;
+ add_category_func = e_m365_event_add_category;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ begin_categories_func = e_m365_task_begin_categories;
+ end_categories_func = e_m365_task_end_categories;
+ add_category_func = e_m365_task_add_category;
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ ecb_m365_extract_categories (new_comp, NULL, &new_value);
+ ecb_m365_extract_categories (old_comp, &old_value, NULL);
+
+ if (!new_value && !old_value)
+ return;
+
+ if (new_value) {
+ GSList *link;
+ gboolean same = FALSE;
+
+ if (old_value && g_hash_table_size (old_value) == g_slist_length (new_value)) {
+ same = TRUE;
+
+ for (link = new_value; link && same; link = g_slist_next (link)) {
+ const gchar *category = link->data;
+
+ same = g_hash_table_contains (old_value, category);
+ }
+ }
+
+ if (!same) {
+ begin_categories_func (builder);
+
+ for (link = new_value; link; link = g_slist_next (link)) {
+ const gchar *category = link->data;
+
+ add_category_func (builder, category);
+ }
+
+ end_categories_func (builder);
+ }
+ } else {
+ begin_categories_func (builder);
+ end_categories_func (builder);
+ }
+
+ if (new_value)
+ g_slist_free_full (new_value, g_free);
+ if (old_value)
+ g_hash_table_destroy (old_value);
+}
+
+static void
+ecb_m365_get_subject (ECalBackendM365 *cbm365,
+ EM365Event *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ const gchar *subject;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ subject = e_m365_event_get_subject (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ subject = e_m365_task_get_subject (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ if (subject)
+ i_cal_component_set_summary (inout_comp, subject);
+}
+
+static void
+ecb_m365_add_subject (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ const gchar *new_value, *old_value;
+
+ new_value = i_cal_component_get_summary (new_comp);
+ old_value = old_comp ? i_cal_component_get_summary (old_comp) : NULL;
+
+ if (g_strcmp0 (new_value, old_value) != 0) {
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ e_m365_event_add_subject (builder, new_value ? new_value : "");
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ e_m365_task_add_subject (builder, new_value ? new_value : "");
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ }
+}
+
+static void
+ecb_m365_get_body (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365ItemBody *value;
+ const gchar *content;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ value = e_m365_event_get_body (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ value = e_m365_task_get_body (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ content = value ? e_m365_item_body_get_content (value) : NULL;
+
+ if (content && *content && strcmp (content, "\r\n") != 0)
+ i_cal_component_set_description (inout_comp, content);
+}
+
+static void
+ecb_m365_add_body (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ const gchar *new_value, *old_value;
+
+ new_value = i_cal_component_get_description (new_comp);
+ old_value = old_comp ? i_cal_component_get_description (old_comp) : NULL;
+
+ if (g_strcmp0 (new_value, old_value) != 0) {
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ e_m365_event_add_body (builder, E_M365_ITEM_BODY_CONTENT_TYPE_TEXT, new_value);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ e_m365_task_add_body (builder, E_M365_ITEM_BODY_CONTENT_TYPE_TEXT, new_value);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ }
+}
+
+static void
+ecb_m365_get_sensitivity (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365SensitivityType value;
+ ICalProperty_Class cls = I_CAL_CLASS_NONE;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ value = e_m365_event_get_sensitivity (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ value = e_m365_task_get_sensitivity (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+
+ if (value == E_M365_SENSITIVITY_NORMAL)
+ cls = I_CAL_CLASS_PUBLIC;
+ else if (value == E_M365_SENSITIVITY_PERSONAL || value == E_M365_SENSITIVITY_PRIVATE)
+ cls = I_CAL_CLASS_PRIVATE;
+ else if (value == E_M365_SENSITIVITY_CONFIDENTIAL)
+ cls = I_CAL_CLASS_CONFIDENTIAL;
+
+ if (cls != I_CAL_CLASS_NONE)
+ i_cal_component_take_property (inout_comp, i_cal_property_new_class (cls));
+}
+
+static void
+ecb_m365_add_sensitivity (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ ICalProperty_Class new_value = I_CAL_CLASS_NONE, old_value = I_CAL_CLASS_NONE;
+ ICalProperty *prop;
+
+ prop = i_cal_component_get_first_property (new_comp, prop_kind);
+
+ if (prop) {
+ new_value = i_cal_property_get_class (prop);
+ g_clear_object (&prop);
+ }
+
+ prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (prop) {
+ old_value = i_cal_property_get_class (prop);
+ g_clear_object (&prop);
+ }
+
+ if (new_value != old_value) {
+ EM365SensitivityType value = E_M365_SENSITIVITY_NOT_SET;
+
+ if (new_value == I_CAL_CLASS_PUBLIC)
+ value = E_M365_SENSITIVITY_NORMAL;
+ else if (new_value == I_CAL_CLASS_PRIVATE)
+ value = E_M365_SENSITIVITY_PRIVATE;
+ else if (new_value == I_CAL_CLASS_CONFIDENTIAL)
+ value = E_M365_SENSITIVITY_CONFIDENTIAL;
+
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ e_m365_event_add_sensitivity (builder, value);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ e_m365_task_add_sensitivity (builder, value);
+ break;
+ default:
+ g_warn_if_reached ();
+ return;
+ }
+ }
+}
+
+static void
+ecb_m365_get_show_as (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365FreeBusyStatusType value;
+ ICalPropertyTransp transp = I_CAL_TRANSP_NONE;
+
+ value = e_m365_event_get_show_as (m365_event);
+
+ if (value == E_M365_FREE_BUSY_STATUS_FREE)
+ transp = I_CAL_TRANSP_TRANSPARENT;
+ else if (value == E_M365_FREE_BUSY_STATUS_BUSY)
+ transp = I_CAL_TRANSP_OPAQUE;
+
+ if (transp != I_CAL_TRANSP_NONE)
+ i_cal_component_take_property (inout_comp, i_cal_property_new_transp (transp));
+}
+
+static void
+ecb_m365_add_show_as (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ ICalPropertyTransp new_value = I_CAL_TRANSP_NONE, old_value = I_CAL_TRANSP_NONE;
+ ICalProperty *prop;
+
+ prop = i_cal_component_get_first_property (new_comp, prop_kind);
+
+ if (prop) {
+ new_value = i_cal_property_get_transp (prop);
+ g_clear_object (&prop);
+ }
+
+ prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (prop) {
+ old_value = i_cal_property_get_transp (prop);
+ g_clear_object (&prop);
+ }
+
+ if (new_value != old_value) {
+ EM365FreeBusyStatusType value = E_M365_FREE_BUSY_STATUS_NOT_SET;
+
+ if (new_value == I_CAL_TRANSP_TRANSPARENT)
+ value = E_M365_FREE_BUSY_STATUS_FREE;
+ else if (new_value == I_CAL_TRANSP_OPAQUE)
+ value = E_M365_FREE_BUSY_STATUS_BUSY;
+
+ e_m365_event_add_show_as (builder, value);
+ }
+}
+
+static void
+ecb_m365_get_location (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365Location *value;
+ const gchar *tmp;
+
+ value = e_m365_event_get_location (m365_event);
+
+ if (!value)
+ return;
+
+ tmp = e_m365_location_get_display_name (value);
+
+ if (tmp && *tmp)
+ i_cal_component_set_location (inout_comp, tmp);
+}
+
+static void
+ecb_m365_add_location (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ const gchar *new_value, *old_value;
+
+ new_value = i_cal_component_get_location (new_comp);
+ old_value = old_comp ? i_cal_component_get_location (old_comp) : NULL;
+
+ if (g_strcmp0 (new_value, old_value) != 0) {
+ if (new_value && *new_value) {
+ e_m365_event_begin_location (builder);
+ e_m365_location_add_display_name (builder, new_value);
+ e_m365_event_end_location (builder);
+ } else {
+ e_m365_event_add_null_location (builder);
+ }
+ }
+}
+
+static void
+ecb_m365_get_organizer (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365Recipient *value;
+ JsonArray *attendees;
+ const gchar *name;
+ const gchar *address;
+
+ value = e_m365_event_get_organizer (m365_event);
+
+ if (!value)
+ return;
+
+ /* Include the organizer only if there is at least one attendee */
+ attendees = e_m365_event_get_attendees (m365_event);
+
+ if (!attendees || !json_array_get_length (attendees))
+ return;
+
+ name = e_m365_recipient_get_name (value);
+ address = e_m365_recipient_get_address (value);
+
+ if (address && *address) {
+ ECalComponentOrganizer *organizer;
+ gchar *mailto_addr;
+
+ mailto_addr = g_strconcat ("mailto:", address, NULL);
+ organizer = e_cal_component_organizer_new ();
+ e_cal_component_organizer_set_value (organizer, mailto_addr);
+ g_free (mailto_addr);
+
+ if (name && *name)
+ e_cal_component_organizer_set_cn (organizer, name);
+
+ i_cal_component_take_property (inout_comp, e_cal_component_organizer_get_as_property
(organizer));
+ e_cal_component_organizer_free (organizer);
+ }
+}
+
+static const gchar *
+ecb_m365_strip_mailto (const gchar *value)
+{
+ if (value && g_ascii_strncasecmp (value, "mailto:", 7) == 0)
+ return value + 7;
+
+ return value;
+}
+
+static void
+ecb_m365_add_organizer (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ ECalComponentOrganizer *new_value = NULL, *old_value = NULL;
+ ICalProperty *prop;
+
+ prop = i_cal_component_get_first_property (new_comp, prop_kind);
+
+ if (prop) {
+ new_value = e_cal_component_organizer_new_from_property (prop);
+ g_clear_object (&prop);
+ }
+
+ prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (prop) {
+ old_value = e_cal_component_organizer_new_from_property (prop);
+ g_clear_object (&prop);
+ }
+
+ if (new_value != old_value && (
+ g_strcmp0 (new_value ? e_cal_component_organizer_get_cn (new_value) : NULL,
+ old_value ? e_cal_component_organizer_get_cn (old_value) : NULL) != 0 ||
+ g_strcmp0 (new_value ? ecb_m365_strip_mailto (e_cal_component_organizer_get_value (new_value)) :
NULL,
+ old_value ? ecb_m365_strip_mailto (e_cal_component_organizer_get_value (old_value)) :
NULL) != 0)) {
+ if (new_value) {
+ e_m365_event_add_organizer (builder,
+ e_cal_component_organizer_get_cn (new_value),
+ ecb_m365_strip_mailto
(e_cal_component_organizer_get_value (new_value)));
+ } else {
+ e_m365_event_add_null_organizer (builder);
+ }
+ }
+
+ e_cal_component_organizer_free (new_value);
+ e_cal_component_organizer_free (old_value);
+}
+
+static void
+ecb_m365_get_attendees (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ JsonArray *array;
+ guint ii, sz;
+
+ array = e_m365_event_get_attendees (m365_event);
+
+ if (!array)
+ return;
+
+ sz = json_array_get_length (array);
+
+ for (ii = 0; ii < sz; ii++) {
+ EM365Attendee *m365_attendee;
+ EM365ResponseStatus *m365_status;
+ EM365AttendeeType m365_att_type;
+ EM365EmailAddress *m365_address;
+ ECalComponentAttendee *e_attendee;
+ ICalParameterRole role = I_CAL_ROLE_NONE;
+ gchar *mailto_addr;
+
+ m365_attendee = json_array_get_object_element (array, ii);
+
+ if (!m365_attendee)
+ continue;
+
+ m365_address = e_m365_attendee_get_email_address (m365_attendee);
+
+ if (!m365_address || !e_m365_email_address_get_address (m365_address))
+ continue;
+
+ e_attendee = e_cal_component_attendee_new ();
+
+ mailto_addr = g_strconcat ("mailto:", e_m365_email_address_get_address (m365_address), NULL);
+ e_cal_component_attendee_set_value (e_attendee, mailto_addr);
+ g_free (mailto_addr);
+
+ if (e_m365_email_address_get_name (m365_address))
+ e_cal_component_attendee_set_cn (e_attendee, e_m365_email_address_get_name
(m365_address));
+
+ m365_status = e_m365_attendee_get_status (m365_attendee);
+
+ if (m365_status) {
+ EM365ResponseType m365_response;
+ ICalParameterPartstat partstat = I_CAL_PARTSTAT_NONE;
+
+ m365_response = e_m365_response_status_get_response (m365_status);
+
+ if (m365_response == E_M365_RESPONSE_TENTATIVELY_ACCEPTED)
+ partstat = I_CAL_PARTSTAT_TENTATIVE;
+ else if (m365_response == E_M365_RESPONSE_ACCEPTED)
+ partstat = I_CAL_PARTSTAT_ACCEPTED;
+ else if (m365_response == E_M365_RESPONSE_DECLINED)
+ partstat = I_CAL_PARTSTAT_DECLINED;
+ else if (m365_response == E_M365_RESPONSE_NOT_RESPONDED)
+ partstat = I_CAL_PARTSTAT_NEEDSACTION;
+
+ if (partstat != I_CAL_PARTSTAT_NONE) {
+ time_t tt;
+
+ e_cal_component_attendee_set_partstat (e_attendee, partstat);
+
+ tt = e_m365_response_status_get_time (m365_status);
+
+ if (tt > (time_t) 0) {
+ ECalComponentParameterBag *params;
+ ICalParameter *param;
+ gchar *tmp;
+
+ tmp = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) tt);
+ params = e_cal_component_attendee_get_parameter_bag (e_attendee);
+
+ param = i_cal_parameter_new_x (tmp);
+ i_cal_parameter_set_xname (param, "X-M365-STATUS-TIME");
+
+ e_cal_component_parameter_bag_take (params, param);
+
+ g_free (tmp);
+ }
+ }
+ }
+
+ m365_att_type = e_m365_attendee_get_type (m365_attendee);
+
+ if (m365_att_type == E_M365_ATTENDEE_REQUIRED) {
+ role = I_CAL_ROLE_REQPARTICIPANT;
+ e_cal_component_attendee_set_cutype (e_attendee, I_CAL_CUTYPE_INDIVIDUAL);
+ } else if (m365_att_type == E_M365_ATTENDEE_OPTIONAL) {
+ role = I_CAL_ROLE_OPTPARTICIPANT;
+ e_cal_component_attendee_set_cutype (e_attendee, I_CAL_CUTYPE_INDIVIDUAL);
+ } else if (m365_att_type == E_M365_ATTENDEE_RESOURCE) {
+ e_cal_component_attendee_set_cutype (e_attendee, I_CAL_CUTYPE_RESOURCE);
+ }
+
+ if (role != I_CAL_ROLE_NONE)
+ e_cal_component_attendee_set_role (e_attendee, role);
+
+ i_cal_component_take_property (inout_comp, e_cal_component_attendee_get_as_property
(e_attendee));
+
+ e_cal_component_attendee_free (e_attendee);
+ }
+}
+
+static void
+ecb_m365_extract_attendees (ICalComponent *comp,
+ GHashTable **out_hash, /* const gchar *ECalComponentAttendee::value ~>
ECalComponentAttendee * */
+ GSList **out_slist) /* ECalComponentAttendee * */
+{
+ ICalProperty *prop;
+
+ if (!comp)
+ return;
+
+ for (prop = i_cal_component_get_first_property (comp, I_CAL_ATTENDEE_PROPERTY);
+ prop;
+ g_object_unref (prop), prop = i_cal_component_get_next_property (comp, I_CAL_ATTENDEE_PROPERTY))
{
+ ECalComponentAttendee *attendee;
+
+ attendee = e_cal_component_attendee_new_from_property (prop);
+
+ if (attendee && e_cal_component_attendee_get_value (attendee)) {
+ if (out_hash) {
+ if (!*out_hash)
+ *out_hash = g_hash_table_new_full (camel_strcase_hash,
camel_strcase_equal, NULL, e_cal_component_attendee_free);
+
+ g_hash_table_insert (*out_hash, (gpointer) e_cal_component_attendee_get_value
(attendee), attendee);
+ } else if (out_slist) {
+ *out_slist = g_slist_prepend (*out_slist, attendee);
+ } else {
+ g_warn_if_reached ();
+ e_cal_component_attendee_free (attendee);
+ }
+ } else {
+ e_cal_component_attendee_free (attendee);
+ }
+ }
+
+ g_clear_object (&prop);
+
+ if (out_slist && *out_slist)
+ *out_slist = g_slist_reverse (*out_slist);
+}
+
+static void
+ecb_m365_add_attendees (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ GHashTable *old_value = NULL;
+ GSList *new_value = NULL;
+
+ ecb_m365_extract_attendees (new_comp, NULL, &new_value);
+ ecb_m365_extract_attendees (old_comp, &old_value, NULL);
+
+ if (!new_value && !old_value)
+ return;
+
+ if (new_value) {
+ GSList *link;
+ gboolean same = FALSE;
+
+ if (old_value && g_hash_table_size (old_value) == g_slist_length (new_value)) {
+ same = TRUE;
+
+ for (link = new_value; link && same; link = g_slist_next (link)) {
+ ECalComponentAttendee *new_att = link->data, *old_att;
+
+ old_att = g_hash_table_lookup (old_value, e_cal_component_attendee_get_value
(new_att));
+
+ same = old_att && e_cal_component_attendee_get_value (old_att) &&
e_cal_component_attendee_get_value (new_att) &&
+ g_ascii_strcasecmp (e_cal_component_attendee_get_value (old_att),
e_cal_component_attendee_get_value (new_att)) == 0 &&
+ g_strcmp0 (e_cal_component_attendee_get_cn (old_att),
e_cal_component_attendee_get_cn (new_att)) == 0 &&
+ e_cal_component_attendee_get_partstat (old_att) ==
e_cal_component_attendee_get_partstat (new_att) &&
+ e_cal_component_attendee_get_cutype (old_att) ==
e_cal_component_attendee_get_cutype (new_att) &&
+ e_cal_component_attendee_get_role (old_att) ==
e_cal_component_attendee_get_role (new_att);
+ }
+ }
+
+ if (!same) {
+ e_m365_event_begin_attendees (builder);
+
+ for (link = new_value; link; link = g_slist_next (link)) {
+ ECalComponentAttendee *attendee = link->data;
+ EM365AttendeeType att_type;
+ EM365ResponseType response = E_M365_RESPONSE_NONE;
+ time_t response_time = (time_t) 0;
+ ICalParameterPartstat partstat;
+ const gchar *address;
+
+ address = ecb_m365_strip_mailto (e_cal_component_attendee_get_value
(attendee));
+
+ if (e_cal_component_attendee_get_cutype (attendee) == I_CAL_CUTYPE_RESOURCE)
+ att_type = E_M365_ATTENDEE_RESOURCE;
+ else if (e_cal_component_attendee_get_role (attendee) ==
I_CAL_ROLE_REQPARTICIPANT ||
+ e_cal_component_attendee_get_role (attendee) == I_CAL_ROLE_CHAIR)
+ att_type = E_M365_ATTENDEE_REQUIRED;
+ else if (e_cal_component_attendee_get_role (attendee) ==
I_CAL_ROLE_OPTPARTICIPANT)
+ att_type = E_M365_ATTENDEE_OPTIONAL;
+ else /* Fallback */
+ att_type = E_M365_ATTENDEE_REQUIRED;
+
+ partstat = e_cal_component_attendee_get_partstat (attendee);
+
+ if (partstat == I_CAL_PARTSTAT_TENTATIVE)
+ response = E_M365_RESPONSE_TENTATIVELY_ACCEPTED;
+ else if (partstat == I_CAL_PARTSTAT_ACCEPTED)
+ response = E_M365_RESPONSE_ACCEPTED;
+ else if (partstat == I_CAL_PARTSTAT_DECLINED)
+ response = E_M365_RESPONSE_DECLINED;
+ else if (partstat == I_CAL_PARTSTAT_NEEDSACTION)
+ response = E_M365_RESPONSE_NOT_RESPONDED;
+
+ if (response != E_M365_RESPONSE_NONE) {
+ ECalComponentParameterBag *params;
+ guint ii, sz;
+
+ params = e_cal_component_attendee_get_parameter_bag (attendee);
+ sz = e_cal_component_parameter_bag_get_count (params);
+
+ for (ii = 0; ii < sz; ii++) {
+ ICalParameter *param;
+
+ param = e_cal_component_parameter_bag_get (params, ii);
+
+ if (param && i_cal_parameter_isa (param) == I_CAL_X_PARAMETER
&&
+ i_cal_parameter_get_xname (param) &&
+ g_ascii_strcasecmp (i_cal_parameter_get_xname (param),
"X-M365-STATUS-TIME") == 0) {
+ const gchar *xvalue;
+
+ xvalue = i_cal_parameter_get_xvalue (param);
+
+ if (xvalue && *xvalue) {
+ gint64 value;
+
+ value = g_ascii_strtoll (xvalue, NULL, 10);
+
+ if (value)
+ response_time = (time_t) value;
+ }
+ }
+ }
+ }
+
+ e_m365_event_add_attendee (builder, att_type, response, response_time,
e_cal_component_attendee_get_cn (attendee), address);
+ }
+
+ e_m365_event_end_attendees (builder);
+ }
+ } else {
+ e_m365_event_add_null_attendees (builder);
+ }
+
+ if (new_value)
+ g_slist_free_full (new_value, e_cal_component_attendee_free);
+ if (old_value)
+ g_hash_table_destroy (old_value);
+}
+
+static void
+ecb_m365_get_importance (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ EM365ImportanceType value;
+ ICalProperty *prop = NULL;
+
+ value = e_m365_event_get_importance (m365_event);
+
+ if (value == E_M365_IMPORTANCE_LOW)
+ prop = i_cal_property_new_priority (9);
+ else if (value == E_M365_IMPORTANCE_NORMAL)
+ prop = i_cal_property_new_priority (5);
+ else if (value == E_M365_IMPORTANCE_HIGH)
+ prop = i_cal_property_new_priority (1);
+
+ if (prop)
+ i_cal_component_take_property (inout_comp, prop);
+}
+
+static void
+ecb_m365_add_importance (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ gint old_value = -1, new_value = -1;
+ ICalProperty *prop;
+
+ prop = i_cal_component_get_first_property (new_comp, prop_kind);
+
+ if (prop) {
+ new_value = i_cal_property_get_priority (prop);
+ g_clear_object (&prop);
+ }
+
+ prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (prop) {
+ old_value = i_cal_property_get_priority (prop);
+ g_clear_object (&prop);
+ }
+
+ if (new_value != old_value) {
+ EM365ImportanceType value = E_M365_IMPORTANCE_NOT_SET;
+
+ if (new_value >= 1 && new_value <= 4) {
+ value = E_M365_IMPORTANCE_HIGH;
+ } else if (new_value == 5) {
+ value = E_M365_IMPORTANCE_NORMAL;
+ } else if (new_value >= 6 && new_value <= 9) {
+ value = E_M365_IMPORTANCE_LOW;
+ }
+
+ e_m365_event_add_importance (builder, value);
+ }
+}
+
+static void
+ecb_m365_get_event_status (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ ICalPropertyStatus status = I_CAL_STATUS_NONE;
+
+ if (e_m365_event_get_is_cancelled (m365_event)) {
+ status = I_CAL_STATUS_CANCELLED;
+ } else {
+ EM365ResponseStatus *response_status;
+
+ response_status = e_m365_event_get_response_status (m365_event);
+
+ if (response_status) {
+ EM365ResponseType response;
+
+ response = e_m365_response_status_get_response (response_status);
+
+ if (response == E_M365_RESPONSE_TENTATIVELY_ACCEPTED)
+ status = I_CAL_STATUS_TENTATIVE;
+ else if (response == E_M365_RESPONSE_ACCEPTED)
+ status = I_CAL_STATUS_CONFIRMED;
+ else if (response == E_M365_RESPONSE_DECLINED)
+ status = I_CAL_STATUS_CANCELLED;
+ else if (response == E_M365_RESPONSE_NOT_RESPONDED)
+ status = I_CAL_STATUS_NEEDSACTION;
+ }
+ }
+
+ if (status != I_CAL_STATUS_NONE)
+ i_cal_component_take_property (inout_comp, i_cal_property_new_status (status));
+}
+
+static ICalRecurrenceWeekday
+ecb_m365_day_of_week_to_ical (EM365DayOfWeekType dow)
+{
+ switch (dow) {
+ case E_M365_DAY_OF_WEEK_SUNDAY:
+ return I_CAL_SUNDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_MONDAY:
+ return I_CAL_MONDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_TUESDAY:
+ return I_CAL_TUESDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_WEDNESDAY:
+ return I_CAL_WEDNESDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_THURSDAY:
+ return I_CAL_THURSDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_FRIDAY:
+ return I_CAL_FRIDAY_WEEKDAY;
+ case E_M365_DAY_OF_WEEK_SATURDAY:
+ return I_CAL_SATURDAY_WEEKDAY;
+ default:
+ break;
+ }
+
+ return I_CAL_NO_WEEKDAY;
+}
+
+static EM365DayOfWeekType
+ecb_m365_day_of_week_from_ical (ICalRecurrenceWeekday dow)
+{
+ switch (dow) {
+ case I_CAL_SUNDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_SUNDAY;
+ break;
+ case I_CAL_MONDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_MONDAY;
+ break;
+ case I_CAL_TUESDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_TUESDAY;
+ break;
+ case I_CAL_WEDNESDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_WEDNESDAY;
+ break;
+ case I_CAL_THURSDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_THURSDAY;
+ break;
+ case I_CAL_FRIDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_FRIDAY;
+ break;
+ case I_CAL_SATURDAY_WEEKDAY:
+ return E_M365_DAY_OF_WEEK_SATURDAY;
+ break;
+ default:
+ break;
+ }
+
+ return E_M365_DAY_OF_WEEK_UNKNOWN;
+}
+
+static void
+ecb_m365_set_index_to_ical (ICalRecurrence *recr,
+ EM365WeekIndexType index)
+{
+ gint by_pos = -2;
+
+ switch (index) {
+ case E_M365_WEEK_INDEX_FIRST:
+ by_pos = 1;
+ break;
+ case E_M365_WEEK_INDEX_SECOND:
+ by_pos = 2;
+ break;
+ case E_M365_WEEK_INDEX_THIRD:
+ by_pos = 3;
+ break;
+ case E_M365_WEEK_INDEX_FOURTH:
+ by_pos = 4;
+ break;
+ case E_M365_WEEK_INDEX_LAST:
+ by_pos = -1;
+ break;
+ default:
+ break;
+ }
+
+ if (by_pos != -2)
+ i_cal_recurrence_set_by_set_pos (recr, 0, by_pos);
+}
+
+static void
+ecb_m365_add_index_from_ical (JsonBuilder *builder,
+ gint by_pos)
+{
+ EM365WeekIndexType index = E_M365_WEEK_INDEX_UNKNOWN;
+
+ if (by_pos == 1)
+ index = E_M365_WEEK_INDEX_FIRST;
+ else if (by_pos == 2)
+ index = E_M365_WEEK_INDEX_SECOND;
+ else if (by_pos == 3)
+ index = E_M365_WEEK_INDEX_THIRD;
+ else if (by_pos == 4)
+ index = E_M365_WEEK_INDEX_FOURTH;
+ else if (by_pos == -1)
+ index = E_M365_WEEK_INDEX_LAST;
+
+ if (index != E_M365_WEEK_INDEX_UNKNOWN)
+ e_m365_recurrence_pattern_add_index (builder, index);
+}
+
+static void
+ecb_m365_set_days_of_week_to_ical (ICalRecurrence *recr,
+ JsonArray *days_of_week)
+{
+ gint ii, jj, sz;
+
+ if (!days_of_week)
+ return;
+
+ ii = 0;
+ sz = json_array_get_length (days_of_week);
+
+ for (jj = 0; jj < sz; jj++) {
+ ICalRecurrenceWeekday week_day;
+
+ week_day = ecb_m365_day_of_week_to_ical (e_m365_array_get_day_of_week_element (days_of_week,
jj));
+
+ if (week_day != I_CAL_SUNDAY_WEEKDAY) {
+ i_cal_recurrence_set_by_day (recr, ii, week_day);
+ ii++;
+ }
+ }
+
+ i_cal_recurrence_set_by_day (recr, ii, I_CAL_RECURRENCE_ARRAY_MAX);
+}
+
+static void
+ecb_m365_add_days_of_week_from_ical (JsonBuilder *builder,
+ ICalRecurrence *recr)
+{
+ gint ii;
+
+ e_m365_recurrence_pattern_begin_days_of_week (builder);
+
+ for (ii = 0; ii < I_CAL_BY_DAY_SIZE; ii++) {
+ ICalRecurrenceWeekday week_day;
+ EM365DayOfWeekType m365_week_day;
+
+ week_day = i_cal_recurrence_get_by_day (recr, ii);
+
+ if (((gint) week_day) == I_CAL_RECURRENCE_ARRAY_MAX)
+ break;
+
+ m365_week_day = ecb_m365_day_of_week_from_ical (week_day);
+
+ if (m365_week_day != E_M365_DAY_OF_WEEK_UNKNOWN)
+ e_m365_recurrence_pattern_add_day_of_week (builder, m365_week_day);
+ }
+
+ e_m365_recurrence_pattern_end_days_of_week (builder);
+}
+
+static gboolean
+ecb_m365_get_recurrence (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365PatternedRecurrence *m365_recr;
+ EM365RecurrencePattern *m365_pattern;
+ EM365RecurrenceRange *m365_range;
+ ICalRecurrence *ical_recr;
+ ICalRecurrenceWeekday week_day;
+ gint month;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ m365_recr = e_m365_event_get_recurrence (m365_object);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ m365_recr = e_m365_task_get_recurrence (m365_object);
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ m365_pattern = m365_recr ? e_m365_patterned_recurrence_get_pattern (m365_recr) : NULL;
+ m365_range = m365_recr ? e_m365_patterned_recurrence_get_range (m365_recr) : NULL;
+
+ if (!m365_recr || !m365_pattern || !m365_range)
+ return TRUE;
+
+ ical_recr = i_cal_recurrence_new ();
+
+ switch (e_m365_recurrence_pattern_get_type (m365_pattern)) {
+ case E_M365_RECURRENCE_PATTERN_DAILY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_DAILY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+ ecb_m365_set_days_of_week_to_ical (ical_recr, e_m365_recurrence_pattern_get_days_of_week
(m365_pattern));
+ break;
+ case E_M365_RECURRENCE_PATTERN_WEEKLY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_WEEKLY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+
+ week_day = ecb_m365_day_of_week_to_ical (e_m365_recurrence_pattern_get_first_day_of_week
(m365_pattern));
+
+ if (week_day != I_CAL_NO_WEEKDAY)
+ i_cal_recurrence_set_week_start (ical_recr, week_day);
+
+ ecb_m365_set_days_of_week_to_ical (ical_recr, e_m365_recurrence_pattern_get_days_of_week
(m365_pattern));
+ break;
+ case E_M365_RECURRENCE_PATTERN_ABSOLUTE_MONTHLY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_MONTHLY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+ i_cal_recurrence_set_by_month_day (ical_recr, 0, e_m365_recurrence_pattern_get_day_of_month
(m365_pattern));
+ break;
+ case E_M365_RECURRENCE_PATTERN_RELATIVE_MONTHLY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_MONTHLY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+ ecb_m365_set_days_of_week_to_ical (ical_recr, e_m365_recurrence_pattern_get_days_of_week
(m365_pattern));
+ week_day = ecb_m365_day_of_week_to_ical (e_m365_recurrence_pattern_get_first_day_of_week
(m365_pattern));
+
+ if (week_day != I_CAL_NO_WEEKDAY)
+ i_cal_recurrence_set_week_start (ical_recr, week_day);
+
+ ecb_m365_set_index_to_ical (ical_recr, e_m365_recurrence_pattern_get_index (m365_pattern));
+ break;
+ case E_M365_RECURRENCE_PATTERN_ABSOLUTE_YEARLY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_YEARLY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+ i_cal_recurrence_set_by_month_day (ical_recr, 0, e_m365_recurrence_pattern_get_day_of_month
(m365_pattern));
+
+ month = e_m365_recurrence_pattern_get_month (m365_pattern);
+
+ if (month >= 1 && month <= 12)
+ i_cal_recurrence_set_by_month (ical_recr, 0, month);
+ break;
+ case E_M365_RECURRENCE_PATTERN_RELATIVE_YEARLY:
+ i_cal_recurrence_set_freq (ical_recr, I_CAL_YEARLY_RECURRENCE);
+ i_cal_recurrence_set_interval (ical_recr, e_m365_recurrence_pattern_get_interval
(m365_pattern));
+ ecb_m365_set_days_of_week_to_ical (ical_recr, e_m365_recurrence_pattern_get_days_of_week
(m365_pattern));
+ week_day = ecb_m365_day_of_week_to_ical (e_m365_recurrence_pattern_get_first_day_of_week
(m365_pattern));
+
+ if (week_day != I_CAL_NO_WEEKDAY)
+ i_cal_recurrence_set_week_start (ical_recr, week_day);
+
+ ecb_m365_set_index_to_ical (ical_recr, e_m365_recurrence_pattern_get_index (m365_pattern));
+
+ month = e_m365_recurrence_pattern_get_month (m365_pattern);
+
+ if (month >= 1 && month <= 12)
+ i_cal_recurrence_set_by_month (ical_recr, 0, month);
+ break;
+ default:
+ g_object_unref (ical_recr);
+ g_warning ("%s: Unknown pattern type: %d", G_STRFUNC, e_m365_recurrence_pattern_get_type
(m365_pattern));
+ /* Ignore the error (in the code) and continue. */
+ return TRUE;
+ }
+
+ switch (e_m365_recurrence_range_get_type (m365_range)) {
+ case E_M365_RECURRENCE_RANGE_ENDDATE:
+ if (e_m365_recurrence_range_get_end_date (m365_range) > 0) {
+ gint yy = 0, mm = 0, dd = 0;
+
+ if (e_m365_date_decode (e_m365_recurrence_range_get_end_date (m365_range), &yy, &mm,
&dd)) {
+ ICalTime *itt;
+
+ itt = i_cal_time_new ();
+ i_cal_time_set_date (itt, yy, mm, dd);
+ i_cal_time_set_is_date (itt, TRUE);
+
+ i_cal_recurrence_set_until (ical_recr, itt);
+
+ g_clear_object (&itt);
+ }
+ }
+ break;
+ case E_M365_RECURRENCE_RANGE_NOEND:
+ break;
+ case E_M365_RECURRENCE_RANGE_NUMBERED:
+ i_cal_recurrence_set_count (ical_recr, e_m365_recurrence_range_get_number_of_occurrences
(m365_range));
+ break;
+ default:
+ g_warning ("%s: Unknown range type: %d", G_STRFUNC, e_m365_recurrence_range_get_type
(m365_range));
+ g_object_unref (ical_recr);
+ /* Ignore the error (in the code) and continue. */
+ return TRUE;
+ }
+
+ i_cal_component_take_property (inout_comp, i_cal_property_new_rrule (ical_recr));
+
+ g_object_unref (ical_recr);
+
+ return TRUE;
+}
+
+static gboolean
+ecb_m365_add_recurrence (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ const gchar *m365_id,
+ JsonBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ICalProperty *new_value, *old_value;
+ gboolean success = TRUE;
+ void (* begin_recurrence_func) (JsonBuilder *builder);
+ void (* end_recurrence_func) (JsonBuilder *builder);
+ void (* add_null_recurrence_func) (JsonBuilder *builder);
+
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ begin_recurrence_func = e_m365_event_begin_recurrence;
+ end_recurrence_func = e_m365_event_end_recurrence;
+ add_null_recurrence_func = e_m365_event_add_null_recurrence;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ begin_recurrence_func = e_m365_task_begin_recurrence;
+ end_recurrence_func = e_m365_task_end_recurrence;
+ add_null_recurrence_func = e_m365_task_add_null_recurrence;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ if (i_cal_component_count_properties (new_comp, prop_kind) > 1) {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Microsoft 365 calendar cannot store more than one recurrence")));
+
+ return FALSE;
+ }
+
+ if (i_cal_component_count_properties (new_comp, I_CAL_RDATE_PROPERTY) > 0 ||
+ i_cal_component_count_properties (new_comp, I_CAL_EXDATE_PROPERTY) > 0 ||
+ i_cal_component_count_properties (new_comp, I_CAL_EXRULE_PROPERTY) > 0) {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Microsoft 365 calendar cannot store component with RDATE, EXDATE or RRULE
properties")));
+
+ return FALSE;
+ }
+
+ new_value = i_cal_component_get_first_property (new_comp, prop_kind);
+ old_value = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (!new_value && !old_value)
+ return TRUE;
+
+ if (new_value) {
+ ICalRecurrence *new_rrule;
+ gboolean same = FALSE;
+
+ new_rrule = i_cal_property_get_rrule (new_value);
+
+ if (old_value && new_rrule) {
+ ICalRecurrence *old_rrule;
+
+ old_rrule = i_cal_property_get_rrule (old_value);
+
+ if (old_rrule) {
+ gchar *new_str, *old_str;
+
+ new_str = i_cal_recurrence_to_string (new_rrule);
+ old_str = i_cal_recurrence_to_string (old_rrule);
+
+ same = g_strcmp0 (new_str, old_str) == 0;
+
+ g_free (new_str);
+ g_free (old_str);
+ }
+
+ g_clear_object (&old_rrule);
+ }
+
+ if (!same && new_rrule) {
+ EM365DayOfWeekType week_day;
+ ICalTime *dtstart;
+ gint by_pos, month, yy = 0, mm = 0, dd = 0;
+
+ begin_recurrence_func (builder);
+ e_m365_patterned_recurrence_begin_pattern (builder);
+
+ switch (i_cal_recurrence_get_freq (new_rrule)) {
+ case I_CAL_DAILY_RECURRENCE:
+ e_m365_recurrence_pattern_add_type (builder, E_M365_RECURRENCE_PATTERN_DAILY);
+ e_m365_recurrence_pattern_add_interval (builder,
i_cal_recurrence_get_interval (new_rrule));
+ ecb_m365_add_days_of_week_from_ical (builder, new_rrule);
+ break;
+ case I_CAL_WEEKLY_RECURRENCE:
+ e_m365_recurrence_pattern_add_type (builder,
E_M365_RECURRENCE_PATTERN_WEEKLY);
+ e_m365_recurrence_pattern_add_interval (builder,
i_cal_recurrence_get_interval (new_rrule));
+
+ week_day = ecb_m365_day_of_week_from_ical (i_cal_recurrence_get_week_start
(new_rrule));
+
+ if (week_day != E_M365_DAY_OF_WEEK_UNKNOWN)
+ e_m365_recurrence_pattern_add_first_day_of_week (builder, week_day);
+
+ ecb_m365_add_days_of_week_from_ical (builder, new_rrule);
+ break;
+ case I_CAL_MONTHLY_RECURRENCE:
+ by_pos = i_cal_recurrence_get_by_set_pos (new_rrule, 0);
+
+ e_m365_recurrence_pattern_add_interval (builder,
i_cal_recurrence_get_interval (new_rrule));
+
+ if (by_pos == I_CAL_RECURRENCE_ARRAY_MAX) {
+ e_m365_recurrence_pattern_add_type (builder,
E_M365_RECURRENCE_PATTERN_ABSOLUTE_MONTHLY);
+ e_m365_recurrence_pattern_add_day_of_month (builder,
i_cal_recurrence_get_by_month_day (new_rrule, 0));
+ } else {
+ e_m365_recurrence_pattern_add_type (builder,
E_M365_RECURRENCE_PATTERN_RELATIVE_MONTHLY);
+
+ week_day = ecb_m365_day_of_week_from_ical
(i_cal_recurrence_get_week_start (new_rrule));
+
+ if (week_day != E_M365_DAY_OF_WEEK_UNKNOWN)
+ e_m365_recurrence_pattern_add_first_day_of_week (builder,
week_day);
+
+ ecb_m365_add_days_of_week_from_ical (builder, new_rrule);
+ ecb_m365_add_index_from_ical (builder, by_pos);
+ }
+ break;
+ case I_CAL_YEARLY_RECURRENCE:
+ by_pos = i_cal_recurrence_get_by_set_pos (new_rrule, 0);
+
+ e_m365_recurrence_pattern_add_interval (builder,
i_cal_recurrence_get_interval (new_rrule));
+
+ month = i_cal_recurrence_get_by_month (new_rrule, 0);
+
+ if (month >= 1 && month <= 12)
+ e_m365_recurrence_pattern_add_month (builder, month);
+
+ if (by_pos == I_CAL_RECURRENCE_ARRAY_MAX) {
+ e_m365_recurrence_pattern_add_type (builder,
E_M365_RECURRENCE_PATTERN_ABSOLUTE_YEARLY);
+ e_m365_recurrence_pattern_add_day_of_month (builder,
i_cal_recurrence_get_by_month_day (new_rrule, 0));
+ } else {
+ e_m365_recurrence_pattern_add_type (builder,
E_M365_RECURRENCE_PATTERN_RELATIVE_YEARLY);
+
+ week_day = ecb_m365_day_of_week_from_ical
(i_cal_recurrence_get_week_start (new_rrule));
+
+ if (week_day != E_M365_DAY_OF_WEEK_UNKNOWN)
+ e_m365_recurrence_pattern_add_first_day_of_week (builder,
week_day);
+
+ ecb_m365_add_days_of_week_from_ical (builder, new_rrule);
+ ecb_m365_add_index_from_ical (builder, by_pos);
+ }
+
+ break;
+ default:
+ g_set_error (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Unknown recurrence frequency (%d)"), i_cal_recurrence_get_freq
(new_rrule));
+
+ success = FALSE;
+ break;
+ }
+
+ e_m365_patterned_recurrence_end_pattern (builder);
+ e_m365_patterned_recurrence_begin_range (builder);
+
+ dtstart = i_cal_component_get_dtstart (new_comp);
+ i_cal_time_get_date (dtstart, &yy, &mm, &dd);
+ g_clear_object (&dtstart);
+
+ e_m365_recurrence_range_add_start_date (builder, e_m365_date_encode (yy, mm, dd));
+
+ if (!i_cal_recurrence_get_count (new_rrule)) {
+ ICalTime *until;
+ gint yy = 0, mm = 0, dd = 0;
+
+ until = i_cal_recurrence_get_until (new_rrule);
+
+ if (until)
+ i_cal_time_get_date (until, &yy, &mm, &dd);
+
+ if (!until || yy == 0) {
+ e_m365_recurrence_range_add_type (builder,
E_M365_RECURRENCE_RANGE_NOEND);
+ } else {
+ e_m365_recurrence_range_add_type (builder,
E_M365_RECURRENCE_RANGE_ENDDATE);
+ e_m365_recurrence_range_add_end_date (builder, e_m365_date_encode
(yy, mm, dd));
+ }
+
+ g_clear_object (&until);
+ } else {
+ e_m365_recurrence_range_add_type (builder, E_M365_RECURRENCE_RANGE_NUMBERED);
+ e_m365_recurrence_range_add_number_of_occurrences (builder,
i_cal_recurrence_get_count (new_rrule));
+ }
+
+ e_m365_patterned_recurrence_end_range (builder);
+ end_recurrence_func (builder);
+ }
+
+ g_clear_object (&new_rrule);
+ } else {
+ add_null_recurrence_func (builder);
+ }
+
+ g_clear_object (&new_value);
+ g_clear_object (&old_value);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_get_reminder (ECalBackendM365 *cbm365,
+ EM365Event *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind,
+ GCancellable *cancellable,
+ GError **error)
+{
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ if (e_m365_event_get_is_reminder_on (m365_object)) {
+ ECalComponentAlarm *alarm;
+ ECalComponentAlarmTrigger *trigger;
+ ICalDuration *duration;
+
+ duration = i_cal_duration_new_from_int (-60 *
e_m365_event_get_reminder_minutes_before_start (m365_object));
+ trigger = e_cal_component_alarm_trigger_new_relative
(E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);
+ g_object_unref (duration);
+
+ alarm = e_cal_component_alarm_new ();
+ e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
+ e_cal_component_alarm_take_summary (alarm, e_cal_component_text_new
(e_m365_event_get_subject (m365_object), NULL));
+ e_cal_component_alarm_take_description (alarm, e_cal_component_text_new
(e_m365_event_get_subject (m365_object), NULL));
+ e_cal_component_alarm_take_trigger (alarm, trigger);
+
+ i_cal_component_take_component (inout_comp, e_cal_component_alarm_get_as_component
(alarm));
+
+ e_cal_component_alarm_free (alarm);
+ }
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ if (e_m365_task_get_is_reminder_on (m365_object)) {
+ EM365DateTimeWithZone *reminder_dt;
+
+ reminder_dt = e_m365_task_get_reminder_date_time (m365_object);
+
+ if (reminder_dt) {
+ ECalComponentAlarm *alarm;
+ ECalComponentAlarmTrigger *trigger;
+ ICalTimezone *tz;
+ ICalTime *itt;
+ time_t tt;
+ const gchar *zone;
+
+ tt = e_m365_date_time_get_date_time (reminder_dt);
+ zone = e_m365_date_time_get_time_zone (reminder_dt);
+
+ if (zone && *zone)
+ zone = e_m365_tz_utils_get_ical_equivalent (zone);
+
+ tz = zone && *zone ? ecb_m365_get_timezone_sync (cbm365, zone) : NULL;
+
+ if (!tz)
+ tz = i_cal_timezone_get_utc_timezone ();
+
+ itt = i_cal_time_new_from_timet_with_zone (tt, FALSE, tz);
+ trigger = e_cal_component_alarm_trigger_new_absolute (itt);
+ g_object_unref (itt);
+
+ alarm = e_cal_component_alarm_new ();
+ e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
+ e_cal_component_alarm_take_summary (alarm, e_cal_component_text_new
(e_m365_task_get_subject (m365_object), NULL));
+ e_cal_component_alarm_take_description (alarm, e_cal_component_text_new
(e_m365_task_get_subject (m365_object), NULL));
+ e_cal_component_alarm_take_trigger (alarm, trigger);
+
+ i_cal_component_take_component (inout_comp,
e_cal_component_alarm_get_as_component (alarm));
+
+ e_cal_component_alarm_free (alarm);
+ }
+ }
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ecb_m365_add_reminder (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ const gchar *m365_id,
+ JsonBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ICalComponent *new_value, *old_value;
+ gboolean success = TRUE;
+
+ if (i_cal_component_count_components (new_comp, I_CAL_VALARM_COMPONENT) > 1) {
+ g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT, _("Microsoft 365
calendar cannot store more that one event reminder")));
+ return FALSE;
+ }
+
+ new_value = i_cal_component_get_first_component (new_comp, I_CAL_VALARM_COMPONENT);
+ old_value = old_comp ? i_cal_component_get_first_component (old_comp, I_CAL_VALARM_COMPONENT) : NULL;
+
+ if (!new_value && !old_value)
+ return TRUE;
+
+ if (new_value) {
+ ECalComponentAlarm *new_alarm;
+ ECalComponentAlarmTrigger *new_trigger;
+ ICalComponentKind kind;
+ ICalDuration *new_duration = NULL;
+ ICalTime *new_absolute_time = NULL;
+ gboolean changed = TRUE;
+
+ kind = i_cal_component_isa (new_comp);
+
+ new_alarm = e_cal_component_alarm_new_from_component (new_value);
+ new_trigger = new_alarm ? e_cal_component_alarm_get_trigger (new_alarm) : NULL;
+
+ switch (kind) {
+ case I_CAL_VEVENT_COMPONENT:
+ success = new_trigger && e_cal_component_alarm_trigger_get_kind (new_trigger) ==
E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
+ if (success) {
+ new_duration = e_cal_component_alarm_trigger_get_duration (new_trigger);
+
+ success = new_duration && i_cal_duration_as_int (new_duration) <= 0;
+ }
+
+ if (!success) {
+ g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT,
_("Microsoft 365 event can have only a reminder before event start")));
+ }
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ success = new_trigger && e_cal_component_alarm_trigger_get_kind (new_trigger) ==
E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE;
+
+ if (success) {
+ new_absolute_time = e_cal_component_alarm_trigger_get_absolute_time
(new_trigger);
+
+ success = new_absolute_time != NULL;
+ }
+
+ if (!success) {
+ g_propagate_error (error, ECC_ERROR_EX (E_CAL_CLIENT_ERROR_INVALID_OBJECT,
_("Microsoft 365 task can have only a reminder with absolute time")));
+ }
+ break;
+ default:
+ g_warn_if_reached ();
+ success = FALSE;
+ break;
+ }
+
+ if (success && old_value && new_trigger) {
+ ECalComponentAlarm *old_alarm;
+ ECalComponentAlarmTrigger *old_trigger;
+
+ old_alarm = e_cal_component_alarm_new_from_component (old_value);
+ old_trigger = old_alarm ? e_cal_component_alarm_get_trigger (old_alarm) : NULL;
+
+ if (old_trigger) {
+ changed = e_cal_component_alarm_trigger_get_kind (new_trigger) !=
e_cal_component_alarm_trigger_get_kind (old_trigger);
+
+ if (!changed) {
+ ICalDuration *old_duration;
+ ICalTime *old_absolute_time;
+
+ switch (kind) {
+ case I_CAL_VEVENT_COMPONENT:
+ old_duration = e_cal_component_alarm_trigger_get_duration
(old_trigger);
+
+ changed = !old_duration || i_cal_duration_as_int
(new_duration) != i_cal_duration_as_int (old_duration);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ old_absolute_time =
e_cal_component_alarm_trigger_get_absolute_time (old_trigger);
+
+ changed = !old_absolute_time || i_cal_time_compare
(new_absolute_time, old_absolute_time) != 0;
+ break;
+ default:
+ g_warn_if_reached ();
+ changed = FALSE;
+ break;
+ }
+ }
+ }
+
+ e_cal_component_alarm_free (old_alarm);
+ }
+
+ if (success && changed) {
+ ICalTimezone *izone = NULL;
+ const gchar *wzone = NULL;
+ time_t tt;
+
+ switch (kind) {
+ case I_CAL_VEVENT_COMPONENT:
+ e_m365_event_add_is_reminder_on (builder, TRUE);
+ e_m365_event_add_reminder_minutes_before_start (builder,
i_cal_duration_as_int (new_duration) / -60);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ izone = i_cal_time_get_timezone (new_absolute_time);
+
+ if (izone)
+ wzone = e_m365_tz_utils_get_msdn_equivalent
(i_cal_timezone_get_location (izone));
+
+ tt = i_cal_time_as_timet_with_zone (new_absolute_time, wzone ? NULL : izone);
+
+ e_m365_task_add_is_reminder_on (builder, TRUE);
+ e_m365_task_add_reminder_date_time (builder, tt, wzone);
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+
+ e_cal_component_alarm_free (new_alarm);
+ } else {
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ e_m365_event_add_is_reminder_on (builder, FALSE);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ e_m365_task_add_is_reminder_on (builder, FALSE);
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+
+ g_clear_object (&new_value);
+ g_clear_object (&old_value);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_get_attachments (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *attachments = NULL, *link;
+ const gchar *id;
+ gboolean success = TRUE;
+
+ switch (i_cal_component_isa (inout_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ if (!e_m365_event_get_has_attachments (m365_object))
+ return TRUE;
+
+ id = e_m365_event_get_id (m365_object);
+
+ if (!e_m365_connection_list_event_attachments_sync (cbm365->priv->cnc, NULL,
+ cbm365->priv->group_id, cbm365->priv->folder_id, id,
"id,name,contentType,contentBytes",
+ &attachments, cancellable, error)) {
+ return FALSE;
+ }
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ if (!e_m365_task_get_has_attachments (m365_object))
+ return TRUE;
+
+ id = e_m365_task_get_id (m365_object);
+
+ if (!e_m365_connection_list_task_attachments_sync (cbm365->priv->cnc, NULL,
+ cbm365->priv->group_id, cbm365->priv->folder_id, id,
"id,name,contentType,contentBytes",
+ &attachments, cancellable, error)) {
+ return FALSE;
+ }
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ for (link = attachments; link && success; link = g_slist_next (link)) {
+ CamelStream *content_stream;
+ EM365Attachment *m365_attach = link->data;
+ gchar *filename;
+
+ if (!m365_attach || e_m365_attachment_get_data_type (m365_attach) !=
E_M365_ATTACHMENT_DATA_TYPE_FILE ||
+ !e_m365_attachment_get_name (m365_attach))
+ continue;
+
+ filename = g_build_filename (cbm365->priv->attachments_dir, id, e_m365_attachment_get_id
(m365_attach), NULL);
+
+ content_stream = camel_stream_fs_new_with_name (filename, O_CREAT | O_TRUNC | O_WRONLY, 0666,
error);
+
+ if (content_stream) {
+ CamelMimeFilter *filter;
+ CamelStream *filter_stream;
+ const gchar *base64_data;
+
+ filter_stream = camel_stream_filter_new (content_stream);
+
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream), filter);
+ g_object_unref (filter);
+
+ base64_data = e_m365_file_attachment_get_content_bytes (m365_attach);
+
+ if (base64_data && *base64_data)
+ success = camel_stream_write (filter_stream, base64_data, strlen
(base64_data), cancellable, error) != -1;
+
+ camel_stream_flush (filter_stream, cancellable, NULL);
+ g_object_unref (filter_stream);
+
+ camel_stream_flush (content_stream, cancellable, NULL);
+ g_object_unref (content_stream);
+
+ if (success) {
+ gchar *uri;
+
+ uri = g_filename_to_uri (filename, NULL, error);
+
+ if (uri) {
+ ICalAttach *ical_attach;
+ ICalParameter *param;
+ ICalProperty *prop;
+ gchar *enc_uri;
+ const gchar *tmp;
+
+ enc_uri = i_cal_value_encode_ical_string (uri);
+ ical_attach = i_cal_attach_new_from_url (enc_uri);
+ prop = i_cal_property_new_attach (ical_attach);
+
+ tmp = e_m365_attachment_get_name (m365_attach);
+
+ if (!tmp || !*tmp)
+ tmp = "attachment.dat";
+
+ param = i_cal_parameter_new_filename (tmp);
+ i_cal_property_take_parameter (prop, param);
+
+ tmp = e_m365_attachment_get_content_type (m365_attach);
+
+ if (tmp && *tmp) {
+ param = i_cal_parameter_new_fmttype (tmp);
+ i_cal_property_take_parameter (prop, param);
+ }
+
+ param = i_cal_parameter_new_x (e_m365_attachment_get_id
(m365_attach));
+ i_cal_parameter_set_xname (param, "X-M365-ATTACHMENTID");
+ i_cal_property_take_parameter (prop, param);
+
+ i_cal_component_take_property (inout_comp, prop);
+
+ g_object_unref (ical_attach);
+ g_free (enc_uri);
+ g_free (uri);
+ } else {
+ success = FALSE;
+ }
+ }
+ } else {
+ success = FALSE;
+ }
+
+ g_free (filename);
+ }
+
+ g_slist_free_full (attachments, (GDestroyNotify) json_object_unref);
+
+ return success;
+}
+
+static void
+ecb_m365_extract_attachments (ICalComponent *comp,
+ GHashTable **out_hash, /* gchar *attachment_id ~> ICalProperty * */
+ GSList **out_slist) /* ICalProperty * */
+{
+ ICalProperty *prop;
+
+ if (!comp)
+ return;
+
+ for (prop = i_cal_component_get_first_property (comp, I_CAL_ATTACH_PROPERTY);
+ prop;
+ g_object_unref (prop), prop = i_cal_component_get_next_property (comp, I_CAL_ATTACH_PROPERTY)) {
+ if (out_slist) {
+ *out_slist = g_slist_prepend (*out_slist, g_object_ref (prop));
+ } else if (out_hash) {
+ gchar *attach_id;
+
+ attach_id = i_cal_property_get_parameter_as_string (prop, "X-M365-ATTACHMENTID");
+ g_warn_if_fail (attach_id != NULL);
+
+ if (attach_id) {
+ if (!*out_hash)
+ *out_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
g_object_unref);
+
+ g_hash_table_insert (*out_hash, attach_id, g_object_ref (prop));
+ }
+ } else {
+ g_warn_if_reached ();
+ }
+ }
+
+ g_clear_object (&prop);
+
+ if (out_slist && *out_slist)
+ *out_slist = g_slist_reverse (*out_slist);
+}
+
+static gboolean
+ecb_m365_add_attachments (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ const gchar *m365_id,
+ JsonBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *new_attachs = NULL;
+ GHashTable *old_attachs = NULL;
+ gboolean (* add_attachment_func) (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *group_id,
+ const gchar *folder_id,
+ const gchar *item_id,
+ JsonBuilder *in_attachment,
+ EM365Attachment **out_attachment,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (* delete_attachment_func) (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *group_id,
+ const gchar *folder_id,
+ const gchar *item_id,
+ const gchar *attachment_id,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean success = TRUE;
+
+ switch (i_cal_component_isa (new_comp)) {
+ case I_CAL_VEVENT_COMPONENT:
+ add_attachment_func = e_m365_connection_add_event_attachment_sync;
+ delete_attachment_func = e_m365_connection_delete_event_attachment_sync;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ add_attachment_func = e_m365_connection_add_task_attachment_sync;
+ delete_attachment_func = e_m365_connection_delete_task_attachment_sync;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ if (!i_cal_component_count_properties (new_comp, I_CAL_ATTACH_PROPERTY) &&
+ !(old_comp ? i_cal_component_count_properties (old_comp, I_CAL_ATTACH_PROPERTY) : 0)) {
+ return TRUE;
+ }
+
+ ecb_m365_extract_attachments (new_comp, NULL, &new_attachs);
+ ecb_m365_extract_attachments (old_comp, &old_attachs, NULL);
+
+ if (new_attachs) {
+ GSList *link, *save_attachs = new_attachs;
+
+ if (old_attachs) {
+ save_attachs = NULL;
+
+ for (link = new_attachs; link; link = g_slist_next (link)) {
+ ICalProperty *prop = link->data;
+ gchar *attach_id;
+
+ attach_id = i_cal_property_get_parameter_as_string (prop,
"X-M365-ATTACHMENTID");
+
+ if (!attach_id || !g_hash_table_remove (old_attachs, attach_id)) {
+ save_attachs = g_slist_prepend (save_attachs, g_object_ref (prop));
+ }
+ }
+
+ if (save_attachs)
+ save_attachs = g_slist_reverse (save_attachs);
+ }
+
+ for (link = save_attachs; link && success; link = g_slist_next (link)) {
+ ICalProperty *prop = link->data;
+ ICalAttach *attach;
+ JsonBuilder *builder = NULL;
+
+ attach = i_cal_property_get_attach (prop);
+
+ if (!attach)
+ continue;
+
+ if (i_cal_attach_get_is_url (attach)) {
+ const gchar *data;
+ gchar *uri;
+
+ data = i_cal_attach_get_url (attach);
+ uri = i_cal_value_decode_ical_string (data);
+
+ if (uri && g_ascii_strncasecmp (uri, "file://", 7) == 0) {
+ CamelStream *content_stream;
+ gchar *filename;
+
+ filename = g_filename_from_uri (uri, NULL, error);
+ content_stream = filename ? camel_stream_fs_new_with_name (filename,
O_RDONLY, 0, error) : NULL;
+
+ if (content_stream) {
+ CamelMimeFilter *filter;
+ CamelStream *filter_stream;
+ CamelStream *base64_stream;
+
+ base64_stream = camel_stream_mem_new ();
+ filter_stream = camel_stream_filter_new (base64_stream);
+
+ filter = camel_mime_filter_basic_new
(CAMEL_MIME_FILTER_BASIC_BASE64_ENC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream),
filter);
+ g_object_unref (filter);
+
+ success = camel_stream_write_to_stream (content_stream,
filter_stream, cancellable, error) != -1;
+
+ camel_stream_flush (filter_stream, cancellable, NULL);
+ g_object_unref (filter_stream);
+
+ /* Ensure the stream is NUL-terminated, thus it can be used
as a string */
+ camel_stream_write (base64_stream, "\0", 1, cancellable,
NULL);
+
+ camel_stream_flush (base64_stream, cancellable, NULL);
+ g_object_unref (content_stream);
+
+ if (success) {
+ GByteArray *bytes;
+
+ bytes = camel_stream_mem_get_byte_array
(CAMEL_STREAM_MEM (base64_stream));
+
+ builder = json_builder_new_immutable ();
+ e_m365_attachment_begin_attachment (builder,
E_M365_ATTACHMENT_DATA_TYPE_FILE);
+ e_m365_file_attachment_add_content_bytes (builder,
(const gchar *) bytes->data);
+ }
+
+ g_object_unref (base64_stream);
+ } else {
+ success = FALSE;
+ }
+
+ g_free (filename);
+ } else {
+ success = FALSE;
+
+ if (uri)
+ g_set_error (error, E_CLIENT_ERROR,
E_CLIENT_ERROR_OTHER_ERROR, _("Cannot store attachment with URI ā%sā"), uri);
+ else
+ g_propagate_error (error, EC_ERROR_EX
(E_CLIENT_ERROR_OTHER_ERROR, _("Failed to read attachment URI")));
+ }
+
+ g_free (uri);
+ } else {
+ const gchar *base64_data;
+
+ base64_data = i_cal_attach_get_data (attach);
+
+ if (base64_data) {
+ builder = json_builder_new_immutable ();
+ e_m365_attachment_begin_attachment (builder,
E_M365_ATTACHMENT_DATA_TYPE_FILE);
+ e_m365_file_attachment_add_content_bytes (builder, base64_data);
+ } else {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR,
_("Failed to get inline attachment data")));
+ }
+ }
+
+ if (builder) {
+ ICalParameter *param;
+ const gchar *tmp;
+
+ param = i_cal_property_get_first_parameter (prop, I_CAL_FILENAME_PARAMETER);
+
+ if (param) {
+ tmp = i_cal_parameter_get_filename (param);
+
+ if (tmp && *tmp)
+ e_m365_attachment_add_name (builder, tmp);
+
+ g_clear_object (¶m);
+ }
+
+ param = i_cal_property_get_first_parameter (prop, I_CAL_FMTTYPE_PARAMETER);
+
+ if (param) {
+ tmp = i_cal_parameter_get_fmttype (param);
+
+ if (tmp && *tmp)
+ e_m365_attachment_add_content_type (builder, tmp);
+ else
+ e_m365_attachment_add_content_type (builder,
"application/octet-stream");
+
+ g_clear_object (¶m);
+ } else {
+ e_m365_attachment_add_content_type (builder,
"application/octet-stream");
+ }
+
+ e_m365_attachment_end_attachment (builder);
+
+ success = add_attachment_func (cbm365->priv->cnc, NULL,
+ cbm365->priv->group_id, cbm365->priv->folder_id, m365_id,
+ builder, NULL, cancellable, error);
+
+ g_object_unref (builder);
+ }
+
+ g_object_unref (attach);
+ }
+
+ if (save_attachs != new_attachs)
+ g_slist_free_full (save_attachs, g_object_unref);
+ }
+
+ if (old_attachs && success) {
+ GHashTableIter iter;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, old_attachs);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL) && success) {
+ const gchar *attachment_id = key;
+
+ success = delete_attachment_func (cbm365->priv->cnc, NULL,
+ cbm365->priv->group_id, cbm365->priv->folder_id, i_cal_component_get_uid
(new_comp),
+ attachment_id, cancellable, error);
+ }
+ }
+
+ if (old_attachs)
+ g_hash_table_destroy (old_attachs);
+ g_slist_free_full (new_attachs, g_object_unref);
+
+ return success;
+}
+
+static void
+ecb_m365_get_task_status (ECalBackendM365 *cbm365,
+ EM365Task *m365_task,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind)
+{
+ ICalPropertyStatus status = I_CAL_STATUS_NONE;
+
+ switch (e_m365_task_get_status (m365_task)) {
+ case E_M365_STATUS_NOT_STARTED:
+ break;
+ case E_M365_STATUS_IN_PROGRESS:
+ case E_M365_STATUS_WAITING_ON_OTHERS:
+ status = I_CAL_STATUS_INPROCESS;
+ break;
+ case E_M365_STATUS_COMPLETED:
+ status = I_CAL_STATUS_COMPLETED;
+ break;
+ case E_M365_STATUS_DEFERRED:
+ status = I_CAL_STATUS_CANCELLED;
+ break;
+ default:
+ break;
+ }
+
+ if (status != I_CAL_STATUS_NONE)
+ i_cal_component_take_property (inout_comp, i_cal_property_new_status (status));
+}
+
+static void
+ecb_m365_add_task_status (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp,
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder)
+{
+ ICalProperty *new_prop, *old_prop;
+ ICalPropertyStatus new_value, old_value;
+
+ new_prop = i_cal_component_get_first_property (new_comp, prop_kind);
+ old_prop = old_comp ? i_cal_component_get_first_property (old_comp, prop_kind) : NULL;
+
+ if (!new_prop && !old_prop)
+ return;
+
+ new_value = new_prop ? i_cal_property_get_status (new_prop) : I_CAL_STATUS_NONE;
+ old_value = old_prop ? i_cal_property_get_status (old_prop) : I_CAL_STATUS_NONE;
+
+ if (new_value != old_value) {
+ EM365StatusType value = E_M365_STATUS_UNKNOWN;
+
+ switch (new_value) {
+ case I_CAL_STATUS_NONE:
+ value = E_M365_STATUS_NOT_STARTED;
+ break;
+ case I_CAL_STATUS_INPROCESS:
+ value = E_M365_STATUS_IN_PROGRESS;
+ break;
+ case I_CAL_STATUS_COMPLETED:
+ value = E_M365_STATUS_COMPLETED;
+ break;
+ case I_CAL_STATUS_CANCELLED:
+ value = E_M365_STATUS_DEFERRED;
+ break;
+ default:
+ break;
+ }
+
+ if (value != E_M365_STATUS_UNKNOWN)
+ e_m365_task_add_status (builder, value);
+ }
+
+ g_clear_object (&new_prop);
+ g_clear_object (&old_prop);
+}
+
+#define SIMPLE_FIELD(propknd, getfn, addfn) { propknd, FALSE, getfn, NULL, addfn, NULL }
+#define COMPLEX_FIELD(propknd, getfn, addfn) { propknd, FALSE, NULL, getfn, NULL, addfn }
+#define COMPLEX_FIELD_2(propknd, getfn, addfn) { propknd, TRUE, NULL, getfn, NULL, addfn }
+
+struct _mappings {
+ ICalPropertyKind prop_kind;
+ gboolean add_in_second_go;
+ void (* get_simple_func) (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind);
+ gboolean (* get_func) (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ ICalComponent *inout_comp,
+ ICalPropertyKind prop_kind,
+ GCancellable *cancellable,
+ GError **error);
+ void (* add_simple_func) (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp, /* nullable */
+ ICalPropertyKind prop_kind,
+ JsonBuilder *builder);
+ gboolean (* add_func) (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp, /* nullable */
+ ICalPropertyKind prop_kind,
+ const gchar *m365_id,
+ JsonBuilder *builder,
+ GCancellable *cancellable,
+ GError **error);
+} event_mappings[] = {
+ SIMPLE_FIELD (I_CAL_UID_PROPERTY, ecb_m365_get_uid, NULL),
+ SIMPLE_FIELD (I_CAL_CREATED_PROPERTY, ecb_m365_get_date_time, NULL),
+ SIMPLE_FIELD (I_CAL_LASTMODIFIED_PROPERTY, ecb_m365_get_date_time, NULL),
+ SIMPLE_FIELD (I_CAL_DTSTART_PROPERTY, ecb_m365_get_date_time_zone,
ecb_m365_add_date_time_zone),
+ SIMPLE_FIELD (I_CAL_DTEND_PROPERTY, ecb_m365_get_date_time_zone,
ecb_m365_add_date_time_zone),
+ SIMPLE_FIELD (I_CAL_CATEGORIES_PROPERTY, ecb_m365_get_categories,
ecb_m365_add_categories),
+ SIMPLE_FIELD (I_CAL_SUMMARY_PROPERTY, ecb_m365_get_subject, ecb_m365_add_subject),
+ SIMPLE_FIELD (I_CAL_DESCRIPTION_PROPERTY, ecb_m365_get_body, ecb_m365_add_body),
+ SIMPLE_FIELD (I_CAL_CLASS_PROPERTY, ecb_m365_get_sensitivity,
ecb_m365_add_sensitivity),
+ SIMPLE_FIELD (I_CAL_TRANSP_PROPERTY, ecb_m365_get_show_as, ecb_m365_add_show_as),
+ SIMPLE_FIELD (I_CAL_LOCATION_PROPERTY, ecb_m365_get_location,
ecb_m365_add_location),
+ SIMPLE_FIELD (I_CAL_ORGANIZER_PROPERTY, ecb_m365_get_organizer,
ecb_m365_add_organizer),
+ SIMPLE_FIELD (I_CAL_ATTENDEE_PROPERTY, ecb_m365_get_attendees,
ecb_m365_add_attendees),
+ SIMPLE_FIELD (I_CAL_PRIORITY_PROPERTY, ecb_m365_get_importance,
ecb_m365_add_importance),
+ SIMPLE_FIELD (I_CAL_STATUS_PROPERTY, ecb_m365_get_event_status, NULL),
+ COMPLEX_FIELD (I_CAL_RRULE_PROPERTY, ecb_m365_get_recurrence,
ecb_m365_add_recurrence),
+ COMPLEX_FIELD (I_CAL_X_PROPERTY, ecb_m365_get_reminder,
ecb_m365_add_reminder),
+ COMPLEX_FIELD_2 (I_CAL_ATTACH_PROPERTY, ecb_m365_get_attachments,
ecb_m365_add_attachments)
+}, task_mappings[] = {
+ SIMPLE_FIELD (I_CAL_UID_PROPERTY, ecb_m365_get_uid, NULL),
+ SIMPLE_FIELD (I_CAL_CREATED_PROPERTY, ecb_m365_get_date_time, NULL),
+ SIMPLE_FIELD (I_CAL_LASTMODIFIED_PROPERTY, ecb_m365_get_date_time, NULL),
+ SIMPLE_FIELD (I_CAL_DTSTART_PROPERTY, ecb_m365_get_date_time_zone,
ecb_m365_add_date_time_zone),
+ SIMPLE_FIELD (I_CAL_DUE_PROPERTY, ecb_m365_get_date_time_zone,
ecb_m365_add_date_time_zone),
+ SIMPLE_FIELD (I_CAL_COMPLETED_PROPERTY, ecb_m365_get_date_time_zone,
ecb_m365_add_date_time_zone),
+ SIMPLE_FIELD (I_CAL_CATEGORIES_PROPERTY, ecb_m365_get_categories,
ecb_m365_add_categories),
+ SIMPLE_FIELD (I_CAL_SUMMARY_PROPERTY, ecb_m365_get_subject, ecb_m365_add_subject),
+ SIMPLE_FIELD (I_CAL_DESCRIPTION_PROPERTY, ecb_m365_get_body, ecb_m365_add_body),
+ SIMPLE_FIELD (I_CAL_CLASS_PROPERTY, ecb_m365_get_sensitivity,
ecb_m365_add_sensitivity),
+ SIMPLE_FIELD (I_CAL_STATUS_PROPERTY, ecb_m365_get_task_status,
ecb_m365_add_task_status),
+ COMPLEX_FIELD (I_CAL_RRULE_PROPERTY, ecb_m365_get_recurrence,
ecb_m365_add_recurrence),
+ COMPLEX_FIELD (I_CAL_X_PROPERTY, ecb_m365_get_reminder,
ecb_m365_add_reminder),
+ COMPLEX_FIELD_2 (I_CAL_ATTACH_PROPERTY, ecb_m365_get_attachments,
ecb_m365_add_attachments)
+};
+
+static const struct _mappings *
+ecb_m365_get_mappings_for_backend (ECalBackendM365 *cbm365,
+ guint *out_n_elements)
+{
+ ICalComponentKind kind;
+
+ kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbm365));
+
+ if (kind == I_CAL_VEVENT_COMPONENT) {
+ *out_n_elements = G_N_ELEMENTS (event_mappings);
+ return event_mappings;
+ }
+
+ if (kind == I_CAL_VTODO_COMPONENT) {
+ *out_n_elements = G_N_ELEMENTS (task_mappings);
+ return task_mappings;
+ }
+
+ g_warn_if_reached ();
+
+ return NULL;
+}
+
+static gchar *
+ecb_m365_join_to_extra (const gchar *change_key,
+ const gchar *ical_comp)
+{
+ if (!change_key && !ical_comp)
+ return NULL;
+
+ return g_strconcat (change_key ? change_key : "", "\n", ical_comp, NULL);
+}
+
+static ICalComponent *
+ecb_m365_json_to_ical (ECalBackendM365 *cbm365,
+ JsonObject *m365_object,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const struct _mappings *mappings;
+ ICalComponent *icomp = NULL;
+ ICalComponentKind kind;
+ guint ii, n_elements = 0;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (m365_object != NULL, NULL);
+
+ mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+ g_return_val_if_fail (mappings != NULL, NULL);
+
+ kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbm365));
+
+ if (kind == I_CAL_VEVENT_COMPONENT)
+ icomp = i_cal_component_new_vevent ();
+ else if (kind == I_CAL_VTODO_COMPONENT)
+ icomp = i_cal_component_new_vtodo ();
+ else
+ g_warn_if_reached ();
+
+ if (!icomp)
+ return NULL;
+
+ for (ii = 0; success && ii < n_elements; ii++) {
+ if (mappings[ii].get_simple_func) {
+ mappings[ii].get_simple_func (cbm365, m365_object, icomp, mappings[ii].prop_kind);
+ } else if (mappings[ii].get_func) {
+ success = mappings[ii].get_func (cbm365, m365_object, icomp, mappings[ii].prop_kind,
cancellable, error);
+ }
+ }
+
+ if (!success)
+ g_clear_object (&icomp);
+
+ return icomp;
+}
+
+static ECalMetaBackendInfo *
+ecb_m365_json_to_ical_nfo (ECalBackendM365 *cbm365,
+ EM365Event *m365_event,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalMetaBackendInfo *nfo;
+ ICalComponent *icomp;
+
+ icomp = ecb_m365_json_to_ical (cbm365, m365_event, cancellable, error);
+
+ if (!icomp)
+ return NULL;
+
+ nfo = e_cal_meta_backend_info_new (i_cal_component_get_uid (icomp),
+ e_m365_event_get_change_key (m365_event),
+ NULL, NULL);
+
+ if (nfo) {
+ nfo->object = i_cal_component_as_ical_string (icomp);
+ nfo->extra = ecb_m365_join_to_extra (e_m365_event_get_change_key (m365_event), nfo->object);
+ }
+
+ g_clear_object (&icomp);
+
+ return nfo;
+}
+
+static JsonBuilder *
+ecb_m365_ical_to_json_locked (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp, /* nullable */
+ GCancellable *cancellable,
+ GError **error)
+{
+ const struct _mappings *mappings;
+ JsonBuilder *builder;
+ guint ii, n_elements = 0;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (new_comp != NULL, NULL);
+
+ mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+ g_return_val_if_fail (mappings != NULL, NULL);
+
+ builder = json_builder_new_immutable ();
+ e_m365_json_begin_object_member (builder, NULL);
+
+ for (ii = 0; success && ii < n_elements; ii++) {
+ if (mappings[ii].add_simple_func) {
+ mappings[ii].add_simple_func (cbm365, new_comp, old_comp, mappings[ii].prop_kind,
builder);
+ } else if (!mappings[ii].add_in_second_go && mappings[ii].add_func) {
+ success = mappings[ii].add_func (cbm365, new_comp, old_comp, mappings[ii].prop_kind,
NULL, builder, cancellable, error);
+ }
+ }
+
+ e_m365_json_end_object_member (builder);
+
+ if (!success)
+ g_clear_object (&builder);
+
+ return builder;
+}
+
+static gboolean
+ecb_m365_ical_to_json_2nd_go_locked (ECalBackendM365 *cbm365,
+ ICalComponent *new_comp,
+ ICalComponent *old_comp, /* nullable */
+ const gchar *m365_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const struct _mappings *mappings;
+ guint ii, n_elements = 0;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (new_comp != NULL, FALSE);
+
+ mappings = ecb_m365_get_mappings_for_backend (cbm365, &n_elements);
+ g_return_val_if_fail (mappings != NULL, FALSE);
+
+ for (ii = 0; success && ii < n_elements; ii++) {
+ if (mappings[ii].add_in_second_go && mappings[ii].add_func) {
+ success = mappings[ii].add_func (cbm365, new_comp, old_comp, mappings[ii].prop_kind,
m365_id, NULL, cancellable, error);
+ }
+ }
+
+ return success;
+}
+
+static gboolean
+ecb_m365_download_changes_locked (ECalBackendM365 *cbm365,
+ const GSList *ids,
+ GSList **out_info_objects,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList *items = NULL, *link;
+
+ if (!ids)
+ return TRUE;
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+ case I_CAL_VEVENT_COMPONENT:
+ if (!e_m365_connection_get_events_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
cbm365->priv->folder_id, ids, NULL, NULL, &items, cancellable, error))
+ return FALSE;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ if (!e_m365_connection_get_tasks_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
cbm365->priv->folder_id, ids, NULL, NULL, &items, cancellable, error))
+ return FALSE;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ for (link = items; link; link = g_slist_next (link)) {
+ JsonObject *item = link->data;
+ ECalMetaBackendInfo *nfo;
+
+ if (!item)
+ continue;
+
+ nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, error);
+
+ if (nfo)
+ *out_info_objects = g_slist_prepend (*out_info_objects, nfo);
+ }
+
+ g_slist_free_full (items, (GDestroyNotify) json_object_unref);
+
+ return TRUE;
+}
+
+static void
+ecb_m365_maybe_disconnect_sync (ECalBackendM365 *cbm365,
+ GError **in_perror,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (E_IS_CAL_BACKEND_M365 (cbm365));
+
+ if (in_perror && g_error_matches (*in_perror, E_CLIENT_ERROR, E_CLIENT_ERROR_AUTHENTICATION_FAILED)) {
+ e_cal_meta_backend_disconnect_sync (E_CAL_META_BACKEND (cbm365), cancellable, NULL);
+ e_backend_schedule_credentials_required (E_BACKEND (cbm365),
E_SOURCE_CREDENTIALS_REASON_REJECTED, NULL, 0, NULL, NULL, G_STRFUNC);
+ }
+}
+
+static gboolean
+ecb_m365_unset_connection_sync (ECalBackendM365 *cbm365,
+ gboolean is_disconnect,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (cbm365), FALSE);
+
+ LOCK (cbm365);
+
+ if (cbm365->priv->cnc) {
+ if (is_disconnect)
+ success = e_m365_connection_disconnect_sync (cbm365->priv->cnc, cancellable, error);
+ }
+
+ g_clear_object (&cbm365->priv->cnc);
+ g_clear_pointer (&cbm365->priv->group_id, g_free);
+ g_clear_pointer (&cbm365->priv->folder_id, g_free);
+
+ UNLOCK (cbm365);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_connect_sync (ECalMetaBackend *meta_backend,
+ const ENamedParameters *credentials,
+ ESourceAuthenticationResult *out_auth_result,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+ EM365FolderKind folder_kind;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (out_auth_result != NULL, FALSE);
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+ case I_CAL_VEVENT_COMPONENT:
+ folder_kind = E_M365_FOLDER_KIND_CALENDAR;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ folder_kind = E_M365_FOLDER_KIND_TASKS;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ cbm365 = E_CAL_BACKEND_M365 (meta_backend);
+
+ LOCK (cbm365);
+
+ if (cbm365->priv->cnc) {
+ UNLOCK (cbm365);
+
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+
+ return TRUE;
+ } else {
+ EBackend *backend;
+ ESourceRegistry *registry;
+ ESource *source;
+ EM365Connection *cnc = NULL;
+ ESourceM365Folder *m365_folder_extension;
+ CamelM365Settings *m365_settings;
+ gchar *group_id;
+ gchar *folder_id;
+
+ backend = E_BACKEND (cbm365);
+ source = e_backend_get_source (backend);
+ registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbm365));
+ m365_settings = camel_m365_settings_get_from_backend (backend, registry);
+ g_warn_if_fail (m365_settings != NULL);
+
+ m365_folder_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
+ group_id = e_source_m365_folder_dup_group_id (m365_folder_extension);
+ folder_id = e_source_m365_folder_dup_id (m365_folder_extension);
+
+ if (folder_id) {
+ cnc = e_m365_connection_new_for_backend (backend, registry, source, m365_settings);
+
+ *out_auth_result = e_m365_connection_authenticate_sync (cnc, NULL, folder_kind,
group_id, folder_id,
+ out_certificate_pem, out_certificate_errors, cancellable, error);
+
+ if (*out_auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+ cbm365->priv->cnc = g_object_ref (cnc);
+
+ g_warn_if_fail (cbm365->priv->group_id == NULL);
+ g_warn_if_fail (cbm365->priv->folder_id == NULL);
+
+ g_free (cbm365->priv->group_id);
+ cbm365->priv->group_id = group_id;
+
+ g_free (cbm365->priv->folder_id);
+ cbm365->priv->folder_id = folder_id;
+
+ group_id = NULL;
+ folder_id = NULL;
+ success = TRUE;
+
+ e_cal_backend_set_writable (E_CAL_BACKEND (cbm365), TRUE);
+ }
+ } else {
+ *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("Folder ID is
not set")));
+ }
+
+ g_clear_object (&cnc);
+ g_free (group_id);
+ g_free (folder_id);
+ }
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_disconnect_sync (ECalMetaBackend *meta_backend,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+
+ return ecb_m365_unset_connection_sync (E_CAL_BACKEND_M365 (meta_backend), TRUE, cancellable, error);
+}
+
+static gboolean
+ecb_m365_get_changes_sync (ECalMetaBackend *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)
+{
+ ECalBackendM365 *cbm365;
+ ECalCache *cal_cache;
+ GSList *items = NULL, *link;
+ gboolean full_read;
+ gboolean success = TRUE;
+ gboolean (* list_items_func) (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *group_id,
+ const gchar *calendar_id,
+ const gchar *prefer_outlook_timezone,
+ const gchar *select,
+ GSList **out_items,
+ GCancellable *cancellable,
+ GError **error);
+ const gchar *(* get_id_func) (JsonObject *item);
+ const gchar *(* get_change_key_func) (JsonObject *item);
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (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_modified_objects != NULL, FALSE);
+ g_return_val_if_fail (out_removed_objects != NULL, FALSE);
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+ case I_CAL_VEVENT_COMPONENT:
+ list_items_func = e_m365_connection_list_events_sync;
+ get_id_func = e_m365_event_get_id;
+ get_change_key_func = e_m365_event_get_change_key;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ list_items_func = e_m365_connection_list_tasks_sync;
+ get_id_func = e_m365_task_get_id;
+ get_change_key_func = e_m365_task_get_change_key;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ *out_created_objects = NULL;
+ *out_modified_objects = NULL;
+ *out_removed_objects = NULL;
+
+ cbm365 = E_CAL_BACKEND_M365 (meta_backend);
+
+ cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+ g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+ LOCK (cbm365);
+
+ full_read = !e_cache_get_count (E_CACHE (cal_cache), E_CACHE_INCLUDE_DELETED, cancellable, NULL);
+
+ success = list_items_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id, cbm365->priv->folder_id,
NULL,
+ full_read ? NULL : "id,changeKey", &items, cancellable, error);
+
+ if (success) {
+ GSList *new_ids = NULL; /* const gchar *, borrowed from 'items' objects */
+ GSList *changed_ids = NULL; /* const gchar *, borrowed from 'items' objects */
+
+ for (link = items; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next
(link)) {
+ JsonObject *item = link->data;
+ const gchar *id, *change_key;
+ gchar *extra = NULL;
+
+ if (!item)
+ continue;
+
+ id = get_id_func (item);
+ change_key = get_change_key_func (item);
+
+ if (e_cal_cache_get_component_extra (cal_cache, id, NULL, &extra, cancellable, NULL))
{
+ const gchar *saved_change_key = NULL;
+
+ ecb_m365_split_extra (extra, &saved_change_key, NULL);
+
+ if (g_strcmp0 (saved_change_key, change_key) == 0) {
+ g_free (extra);
+ continue;
+ } else if (full_read) {
+ ECalMetaBackendInfo *nfo;
+
+ nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, NULL);
+
+ if (nfo)
+ *out_modified_objects = g_slist_prepend
(*out_modified_objects, nfo);
+ } else {
+ changed_ids = g_slist_prepend (changed_ids, (gpointer) id);
+ }
+
+ g_free (extra);
+ } else if (full_read) {
+ ECalMetaBackendInfo *nfo;
+
+ nfo = ecb_m365_json_to_ical_nfo (cbm365, item, cancellable, NULL);
+
+ if (nfo)
+ *out_created_objects = g_slist_prepend (*out_created_objects, nfo);
+ } else {
+ new_ids = g_slist_prepend (new_ids, (gpointer) id);
+ }
+ }
+
+ if (new_ids) {
+ new_ids = g_slist_reverse (new_ids);
+ success = ecb_m365_download_changes_locked (cbm365, new_ids, out_created_objects,
cancellable, error);
+ }
+
+ if (success && changed_ids) {
+ changed_ids = g_slist_reverse (changed_ids);
+ success = ecb_m365_download_changes_locked (cbm365, changed_ids,
out_modified_objects, cancellable, error);
+ }
+
+ g_slist_free (new_ids);
+ g_slist_free (changed_ids);
+ }
+
+ g_slist_free_full (items, (GDestroyNotify) json_object_unref);
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+
+ g_clear_object (&cal_cache);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_load_component_sync (ECalMetaBackend *meta_backend,
+ const gchar *uid,
+ const gchar *extra,
+ ICalComponent **out_component,
+ gchar **out_extra,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+ JsonObject *item = NULL;
+ const gchar *(* get_change_key_func) (JsonObject *item);
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_component != NULL, FALSE);
+ g_return_val_if_fail (out_extra != NULL, FALSE);
+
+ cbm365 = E_CAL_BACKEND_M365 (meta_backend);
+
+ LOCK (cbm365);
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+ case I_CAL_VEVENT_COMPONENT:
+ success = e_m365_connection_get_event_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, NULL, NULL, &item, cancellable, error);
+ get_change_key_func = e_m365_event_get_change_key;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ success = e_m365_connection_get_task_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, NULL, NULL, &item, cancellable, error);
+ get_change_key_func = e_m365_task_get_change_key;
+ break;
+ default:
+ success = FALSE;
+ break;
+ }
+
+ if (success) {
+ *out_component = ecb_m365_json_to_ical (cbm365, item, cancellable, error);
+
+ if (*out_component) {
+ gchar *ical_str;
+
+ ical_str = i_cal_component_as_ical_string (*out_component);
+
+ *out_extra = ecb_m365_join_to_extra (get_change_key_func (item), ical_str);
+
+ g_free (ical_str);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_save_component_sync (ECalMetaBackend *meta_backend,
+ gboolean overwrite_existing,
+ EConflictResolution conflict_resolution,
+ const GSList *instances,
+ const gchar *extra,
+ guint32 opflags,
+ gchar **out_new_uid,
+ gchar **out_new_extra,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+ ICalComponent *new_comp, *old_comp = NULL;
+ JsonBuilder *builder;
+ gboolean success = FALSE;
+ gboolean (* create_item_func) (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *group_id,
+ const gchar *folder_id,
+ JsonBuilder *item,
+ JsonObject **out_created_item,
+ GCancellable *cancellable,
+ GError **error);
+ gboolean (* update_item_func) (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *group_id,
+ const gchar *folder_id,
+ const gchar *item_id,
+ JsonBuilder *item,
+ GCancellable *cancellable,
+ GError **error);
+ const gchar *(* get_id_func) (JsonObject *item);
+ const gchar *(* get_change_key_func) (JsonObject *item);
+
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (instances != NULL, FALSE);
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend))) {
+ case I_CAL_VEVENT_COMPONENT:
+ if (instances->next) {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Can store only simple events into Microsoft 365 calendar")));
+
+ return FALSE;
+ }
+
+ create_item_func = e_m365_connection_create_event_sync;
+ update_item_func = e_m365_connection_update_event_sync;
+ get_id_func = e_m365_event_get_id;
+ get_change_key_func = e_m365_event_get_change_key;
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ if (instances->next) {
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_NOT_SUPPORTED,
+ _("Can store only simple tasks into Microsoft 365 task folder")));
+
+ return FALSE;
+ }
+
+ create_item_func = e_m365_connection_create_task_sync;
+ update_item_func = e_m365_connection_update_task_sync;
+ get_id_func = e_m365_task_get_id;
+ get_change_key_func = e_m365_task_get_change_key;
+ break;
+ default:
+ g_warn_if_reached ();
+ return FALSE;
+ }
+
+ cbm365 = E_CAL_BACKEND_M365 (meta_backend);
+
+ LOCK (cbm365);
+
+ new_comp = e_cal_component_get_icalcomponent (instances->data);
+
+ if (extra && *extra) {
+ const gchar *comp_str;
+
+ comp_str = ecb_m365_get_component_from_extra (extra);
+
+ if (comp_str)
+ old_comp = i_cal_component_new_from_string (comp_str);
+ }
+
+ builder = ecb_m365_ical_to_json_locked (cbm365, new_comp, old_comp, cancellable, error);
+
+ if (builder) {
+ if (overwrite_existing) {
+ const gchar *uid = i_cal_component_get_uid (new_comp);
+
+ success = update_item_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, builder, cancellable, error);
+
+ if (success)
+ success = ecb_m365_ical_to_json_2nd_go_locked (cbm365, new_comp, old_comp,
uid, cancellable, error);
+
+ if (success) {
+ /* To re-read it from the server */
+ *out_new_uid = g_strdup (uid);
+ }
+ } else {
+ JsonObject *created_item = NULL;
+
+ success = create_item_func (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, builder, &created_item, cancellable, error);
+
+ if (success && created_item) {
+ const gchar *m365_id = get_id_func (created_item);
+
+ success = ecb_m365_ical_to_json_2nd_go_locked (cbm365, new_comp, old_comp,
m365_id, cancellable, error);
+ }
+
+ if (success && created_item) {
+ ICalComponent *icomp;
+
+ *out_new_uid = g_strdup (get_id_func (created_item));
+
+ icomp = ecb_m365_json_to_ical (cbm365, created_item, cancellable, error);
+
+ if (icomp) {
+ gchar *ical_str;
+
+ ical_str = i_cal_component_as_ical_string (icomp);
+
+ *out_new_extra = ecb_m365_join_to_extra (get_change_key_func
(created_item), ical_str);
+
+ g_clear_object (&icomp);
+ g_free (ical_str);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ if (created_item)
+ json_object_unref (created_item);
+ }
+
+ g_clear_object (&builder);
+ }
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+
+ g_clear_object (&old_comp);
+
+ return success;
+}
+
+static gboolean
+ecb_m365_remove_component_sync (ECalMetaBackend *meta_backend,
+ EConflictResolution conflict_resolution,
+ const gchar *uid,
+ const gchar *extra,
+ const gchar *object,
+ guint32 opflags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (meta_backend), FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ cbm365 = E_CAL_BACKEND_M365 (meta_backend);
+
+ LOCK (cbm365);
+
+ switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbm365))) {
+ case I_CAL_VEVENT_COMPONENT:
+ success = e_m365_connection_delete_event_sync (cbm365->priv->cnc, NULL,
cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, cancellable, error);
+ break;
+ case I_CAL_VTODO_COMPONENT:
+ success = e_m365_connection_delete_task_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, cancellable, error);
+ break;
+ default:
+ g_warn_if_reached ();
+ success = FALSE;
+ }
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+
+ return success;
+}
+
+static void
+ecb_m365_discard_alarm_sync (ECalBackendSync *cal_backend_sync,
+ EDataCal *cal,
+ GCancellable *cancellable,
+ const gchar *uid,
+ const gchar *rid,
+ const gchar *auid,
+ guint32 opflags,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+
+ g_return_if_fail (E_IS_CAL_BACKEND_M365 (cal_backend_sync));
+ g_return_if_fail (uid != NULL);
+
+ if (e_cal_backend_get_kind (E_CAL_BACKEND (cal_backend_sync)) != I_CAL_VEVENT_COMPONENT) {
+ g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
+ return;
+ }
+
+ cbm365 = E_CAL_BACKEND_M365 (cal_backend_sync);
+
+ if (!e_cal_meta_backend_ensure_connected_sync (E_CAL_META_BACKEND (cbm365), cancellable, error))
+ return;
+
+ LOCK (cbm365);
+
+ e_m365_connection_dismiss_reminder_sync (cbm365->priv->cnc, NULL, cbm365->priv->group_id,
+ cbm365->priv->folder_id, uid, cancellable, error);
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+}
+
+static void
+ecb_m365_get_free_busy_sync (ECalBackendSync *cal_backend_sync,
+ EDataCal *cal,
+ GCancellable *cancellable,
+ const GSList *users,
+ time_t start,
+ time_t end,
+ GSList **out_freebusyobjs,
+ GError **error)
+{
+ ECalBackendM365 *cbm365;
+ GSList *infos = NULL;
+ gboolean success;
+
+ g_return_if_fail (E_IS_CAL_BACKEND_M365 (cal_backend_sync));
+ g_return_if_fail (users != NULL);
+ g_return_if_fail (out_freebusyobjs != NULL);
+
+ if (e_cal_backend_get_kind (E_CAL_BACKEND (cal_backend_sync)) != I_CAL_VEVENT_COMPONENT) {
+ g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_NOT_SUPPORTED));
+ return;
+ }
+
+ cbm365 = E_CAL_BACKEND_M365 (cal_backend_sync);
+
+ if (!e_cal_meta_backend_ensure_connected_sync (E_CAL_META_BACKEND (cbm365), cancellable, error))
+ return;
+
+ LOCK (cbm365);
+
+ success = e_m365_connection_get_schedule_sync (cbm365->priv->cnc, NULL, 30, start, end, users,
&infos, cancellable, error);
+
+ UNLOCK (cbm365);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+
+ if (success) {
+ ICalTimezone *utc_zone = i_cal_timezone_get_utc_timezone ();
+ GSList *link;
+
+ *out_freebusyobjs = NULL;
+
+ for (link = infos; link; link = g_slist_next (link)) {
+ EM365ScheduleInformation *schinfo = link->data;
+ ICalComponent *vfb = NULL;
+ JsonArray *array;
+ guint ii, sz;
+
+ if (!schinfo || !e_m365_schedule_information_get_schedule_id (schinfo))
+ continue;
+
+ array = e_m365_schedule_information_get_schedule_items (schinfo);
+ sz = array ? json_array_get_length (array) : 0;
+
+ for (ii = 0; ii < sz; ii++) {
+ EM365ScheduleItem *schitem = json_array_get_object_element (array, ii);
+ EM365DateTimeWithZone *dt;
+ ICalProperty *prop;
+ ICalPeriod *ipt;
+ ICalTime *itt;
+ const gchar *tmp;
+
+ if (!schitem || !e_m365_schedule_item_get_start (schitem) ||
!e_m365_schedule_item_get_end (schitem))
+ continue;
+
+ ipt = i_cal_period_new_null_period ();
+
+ dt = e_m365_schedule_item_get_start (schitem);
+ itt = i_cal_time_new_from_timet_with_zone (e_m365_date_time_get_date_time
(dt), 0, utc_zone);
+ i_cal_period_set_start (ipt, itt);
+ g_clear_object (&itt);
+
+ dt = e_m365_schedule_item_get_end (schitem);
+ itt = i_cal_time_new_from_timet_with_zone (e_m365_date_time_get_date_time
(dt), 0, utc_zone);
+ i_cal_period_set_end (ipt, itt);
+ g_clear_object (&itt);
+
+ prop = i_cal_property_new_freebusy (ipt);
+ g_clear_object (&ipt);
+
+ switch (e_m365_schedule_item_get_status (schitem)) {
+ case E_M365_FREE_BUSY_STATUS_FREE:
+ i_cal_property_set_parameter_from_string (prop, "FBTYPE", "FREE");
+ break;
+ case E_M365_FREE_BUSY_STATUS_TENTATIVE:
+ i_cal_property_set_parameter_from_string (prop, "FBTYPE",
"BUSY-TENTATIVE");
+ break;
+ case E_M365_FREE_BUSY_STATUS_BUSY:
+ i_cal_property_set_parameter_from_string (prop, "FBTYPE", "BUSY");
+ break;
+ case E_M365_FREE_BUSY_STATUS_OOF:
+ case E_M365_FREE_BUSY_STATUS_WORKING_ELSEWHERE:
+ i_cal_property_set_parameter_from_string (prop, "FBTYPE",
"BUSY-UNAVAILABLE");
+ break;
+ default:
+ break;
+ }
+
+ tmp = e_m365_schedule_item_get_subject (schitem);
+
+ if (tmp && *tmp)
+ i_cal_property_set_parameter_from_string (prop, "X-SUMMARY", tmp);
+
+ tmp = e_m365_schedule_item_get_location (schitem);
+
+ if (tmp && *tmp)
+ i_cal_property_set_parameter_from_string (prop, "X-LOCATION", tmp);
+
+ if (!vfb)
+ vfb = i_cal_component_new_vfreebusy ();
+
+ i_cal_component_take_property (vfb, prop);
+ }
+
+ if (vfb) {
+ gchar *mailto;
+
+ mailto = g_strconcat ("mailto:", e_m365_schedule_information_get_schedule_id
(schinfo), NULL);
+ i_cal_component_take_property (vfb, i_cal_property_new_attendee (mailto));
+ g_free (mailto);
+
+ *out_freebusyobjs = g_slist_prepend (*out_freebusyobjs,
i_cal_component_as_ical_string (vfb));
+
+ g_clear_object (&vfb);
+ }
+ }
+
+ *out_freebusyobjs = g_slist_reverse (*out_freebusyobjs);
+ }
+
+ g_slist_free_full (infos, (GDestroyNotify) json_object_unref);
+
+ ecb_m365_convert_error_to_client_error (error);
+ ecb_m365_maybe_disconnect_sync (cbm365, error, cancellable);
+}
+
+static gchar *
+ecb_m365_get_backend_property (ECalBackend *cal_backend,
+ const gchar *prop_name)
+{
+ ECalBackendM365 *cbm365;
+
+ g_return_val_if_fail (E_IS_CAL_BACKEND_M365 (cal_backend), NULL);
+ g_return_val_if_fail (prop_name != NULL, NULL);
+
+ cbm365 = E_CAL_BACKEND_M365 (cal_backend);
+
+ if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+ return g_strjoin (
+ ",",
+ E_CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS,
+ E_CAL_STATIC_CAPABILITY_NO_AUDIO_ALARMS,
+ E_CAL_STATIC_CAPABILITY_NO_PROCEDURE_ALARMS,
+ E_CAL_STATIC_CAPABILITY_ONE_ALARM_ONLY,
+ E_CAL_STATIC_CAPABILITY_REMOVE_ALARMS,
+ E_CAL_STATIC_CAPABILITY_NO_THISANDPRIOR,
+ E_CAL_STATIC_CAPABILITY_NO_THISANDFUTURE,
+ E_CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK,
+ E_CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT,
+ E_CAL_STATIC_CAPABILITY_SAVE_SCHEDULES,
+ E_CAL_STATIC_CAPABILITY_NO_ALARM_AFTER_START,
+ E_CAL_STATIC_CAPABILITY_NO_MEMO_START_DATE,
+ E_CAL_STATIC_CAPABILITY_ALL_DAY_EVENT_AS_TIME,
+ E_CAL_STATIC_CAPABILITY_TASK_DATE_ONLY,
+ E_CAL_STATIC_CAPABILITY_TASK_NO_ALARM,
+ E_CAL_STATIC_CAPABILITY_TASK_CAN_RECUR,
+ E_CAL_STATIC_CAPABILITY_TASK_HANDLE_RECUR,
+ e_cal_meta_backend_get_capabilities (E_CAL_META_BACKEND (cbm365)),
+ NULL);
+ } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS)) {
+ /* return email address of the person who opened the calendar */
+ CamelM365Settings *m365_settings;
+
+ m365_settings = camel_m365_settings_get_from_backend (E_BACKEND (cal_backend),
e_cal_backend_get_registry (cal_backend));
+
+ return camel_m365_settings_dup_email (m365_settings);
+ } else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
+ /* Microsoft 365 does not support email based alarms */
+ return NULL;
+ }
+
+ /* Chain up to parent's method. */
+ return E_CAL_BACKEND_CLASS (e_cal_backend_m365_parent_class)->impl_get_backend_property (cal_backend,
prop_name);
+}
+
+static gboolean
+ecb_m365_get_destination_address (EBackend *backend,
+ gchar **host,
+ guint16 *port)
+{
+ g_return_val_if_fail (port != NULL, FALSE);
+ g_return_val_if_fail (host != NULL, FALSE);
+
+ /* Sanity checking */
+ if (!e_cal_backend_get_registry (E_CAL_BACKEND (backend)) ||
+ !e_backend_get_source (backend))
+ return FALSE;
+
+ *host = g_strdup ("graph.microsoft.com");
+ *port = 443;
+
+ return TRUE;
+}
+
+static gchar *
+ecb_m365_dup_component_revision (ECalCache *cal_cache,
+ ICalComponent *icomp,
+ gpointer user_data)
+{
+ g_return_val_if_fail (icomp != NULL, NULL);
+
+ return e_cal_util_component_dup_x_property (icomp, "X-EVOLUTION-CHANGEKEY");
+}
+
+static void
+ecb_m365_constructed (GObject *object)
+{
+ ECalBackendM365 *cbm365 = E_CAL_BACKEND_M365 (object);
+ ECalCache *cal_cache;
+ gchar *cache_dirname;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_m365_parent_class)->constructed (object);
+
+ /* Reset the connectable, it steals data from Authentication extension,
+ where is written incorrect address */
+ e_backend_set_connectable (E_BACKEND (object), NULL);
+
+ cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cbm365));
+ g_return_if_fail (cal_cache != NULL);
+
+ cache_dirname = g_path_get_dirname (e_cache_get_filename (E_CACHE (cal_cache)));
+ g_signal_connect (cal_cache, "dup-component-revision", G_CALLBACK (ecb_m365_dup_component_revision),
NULL);
+
+ g_clear_object (&cal_cache);
+
+ cbm365->priv->attachments_dir = g_build_filename (cache_dirname, "attachments", NULL);
+ g_mkdir_with_parents (cbm365->priv->attachments_dir, 0777);
+
+ g_free (cache_dirname);
+
+ e_m365_tz_utils_ref_windows_zones ();
+}
+
+static void
+ecb_m365_dispose (GObject *object)
+{
+ ECalBackendM365 *cbm365 = E_CAL_BACKEND_M365 (object);
+
+ ecb_m365_unset_connection_sync (cbm365, FALSE, NULL, NULL);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_m365_parent_class)->dispose (object);
+}
+
+static void
+ecb_m365_finalize (GObject *object)
+{
+ ECalBackendM365 *cbm365 = E_CAL_BACKEND_M365 (object);
+
+ g_free (cbm365->priv->attachments_dir);
+
+ g_rec_mutex_clear (&cbm365->priv->property_lock);
+
+ e_m365_tz_utils_unref_windows_zones ();
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_cal_backend_m365_parent_class)->finalize (object);
+}
+
+static void
+e_cal_backend_m365_init (ECalBackendM365 *cbm365)
+{
+ cbm365->priv = e_cal_backend_m365_get_instance_private (cbm365);
+
+ g_rec_mutex_init (&cbm365->priv->property_lock);
+}
+
+static void
+e_cal_backend_m365_class_init (ECalBackendM365Class *klass)
+{
+ GObjectClass *object_class;
+ EBackendClass *backend_class;
+ ECalBackendClass *cal_backend_class;
+ ECalBackendSyncClass *cal_backend_sync_class;
+ ECalMetaBackendClass *cal_meta_backend_class;
+
+ cal_meta_backend_class = E_CAL_META_BACKEND_CLASS (klass);
+ cal_meta_backend_class->connect_sync = ecb_m365_connect_sync;
+ cal_meta_backend_class->disconnect_sync = ecb_m365_disconnect_sync;
+ cal_meta_backend_class->get_changes_sync = ecb_m365_get_changes_sync;
+ cal_meta_backend_class->load_component_sync = ecb_m365_load_component_sync;
+ cal_meta_backend_class->save_component_sync = ecb_m365_save_component_sync;
+ cal_meta_backend_class->remove_component_sync = ecb_m365_remove_component_sync;
+
+ cal_backend_sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);
+ cal_backend_sync_class->discard_alarm_sync = ecb_m365_discard_alarm_sync;
+ cal_backend_sync_class->get_free_busy_sync = ecb_m365_get_free_busy_sync;
+
+ cal_backend_class = E_CAL_BACKEND_CLASS (klass);
+ cal_backend_class->impl_get_backend_property = ecb_m365_get_backend_property;
+
+ backend_class = E_BACKEND_CLASS (klass);
+ backend_class->get_destination_address = ecb_m365_get_destination_address;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = ecb_m365_constructed;
+ object_class->dispose = ecb_m365_dispose;
+ object_class->finalize = ecb_m365_finalize;
+}
diff --git a/src/Microsoft365/calendar/e-cal-backend-m365.h b/src/Microsoft365/calendar/e-cal-backend-m365.h
new file mode 100644
index 00000000..7963dcae
--- /dev/null
+++ b/src/Microsoft365/calendar/e-cal-backend-m365.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_CAL_BACKEND_M365_H
+#define E_CAL_BACKEND_M365_H
+
+#include <libedata-cal/libedata-cal.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_CAL_BACKEND_M365 (e_cal_backend_m365_get_type ())
+#define E_CAL_BACKEND_M365(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_CAL_BACKEND_M365,
ECalBackendM365))
+#define E_CAL_BACKEND_M365_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_CAL_BACKEND_M365,
ECalBackendM365Class))
+#define E_IS_CAL_BACKEND_M365(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_CAL_BACKEND_M365))
+#define E_IS_CAL_BACKEND_M365_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_CAL_BACKEND_M365))
+
+typedef struct _ECalBackendM365 ECalBackendM365;
+typedef struct _ECalBackendM365Class ECalBackendM365Class;
+typedef struct _ECalBackendM365Private ECalBackendM365Private;
+
+struct _ECalBackendM365 {
+ ECalMetaBackend parent_object;
+ ECalBackendM365Private *priv;
+};
+
+struct _ECalBackendM365Class {
+ ECalMetaBackendClass parent_class;
+};
+
+GType e_cal_backend_m365_get_type (void);
+
+G_END_DECLS
+
+#endif /* E_CAL_BACKEND_M365_H */
diff --git a/src/Microsoft365/camel/CMakeLists.txt b/src/Microsoft365/camel/CMakeLists.txt
new file mode 100644
index 00000000..928b837a
--- /dev/null
+++ b/src/Microsoft365/camel/CMakeLists.txt
@@ -0,0 +1,75 @@
+install(FILES libcamelmicrosoft365.urls
+ DESTINATION ${camel_providerdir}
+)
+
+set(DEPENDENCIES
+ evolution-microsoft365
+)
+
+set(SOURCES
+ camel-m365-folder.c
+ camel-m365-folder.h
+ camel-m365-folder-summary.c
+ camel-m365-folder-summary.h
+ camel-m365-message-info.c
+ camel-m365-message-info.h
+ camel-m365-provider.c
+ camel-m365-store.c
+ camel-m365-store.h
+ camel-m365-store-summary.c
+ camel-m365-store-summary.h
+ camel-m365-transport.c
+ camel-m365-transport.h
+ camel-m365-utils.c
+ camel-m365-utils.h
+)
+
+add_library(camelmicrosoft365 MODULE
+ ${SOURCES}
+)
+
+add_dependencies(camelmicrosoft365
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(camelmicrosoft365 PRIVATE
+ -DG_LOG_DOMAIN=\"camel-microsoft365-provider\"
+ -DM365_LOCALEDIR=\"${LOCALE_INSTALL_DIR}\"
+)
+
+target_compile_options(camelmicrosoft365 PUBLIC
+ ${CAMEL_CFLAGS}
+ ${EVOLUTION_SHELL_CFLAGS}
+ ${EVOLUTION_MAIL_CFLAGS}
+ ${LIBEDATASERVER_CFLAGS}
+ ${LIBECAL_CFLAGS}
+ ${SOUP_CFLAGS}
+)
+
+target_include_directories(camelmicrosoft365 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/src/Microsoft365
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${EVOLUTION_SHELL_INCLUDE_DIRS}
+ ${EVOLUTION_MAIL_INCLUDE_DIRS}
+ ${LIBEDATASERVER_INCLUDE_DIRS}
+ ${LIBECAL_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+)
+
+target_link_libraries(camelmicrosoft365
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+ ${EVOLUTION_SHELL_LDFLAGS}
+ ${EVOLUTION_MAIL_LDFLAGS}
+ ${LIBEDATASERVER_LDFLAGS}
+ ${LIBECAL_LDFLAGS}
+ ${SOUP_LDFLAGS}
+)
+
+install(TARGETS camelmicrosoft365
+ DESTINATION ${camel_providerdir}
+)
diff --git a/src/Microsoft365/camel/camel-m365-folder-summary.c
b/src/Microsoft365/camel/camel-m365-folder-summary.c
new file mode 100644
index 00000000..1d46f026
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-folder-summary.c
@@ -0,0 +1,306 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib.h>
+
+#include "camel-m365-folder.h"
+
+#include "camel-m365-folder-summary.h"
+
+#define LOCK(_summary) g_mutex_lock (&_summary->priv->property_lock)
+#define UNLOCK(_summary) g_mutex_unlock (&_summary->priv->property_lock)
+
+struct _CamelM365FolderSummaryPrivate {
+ GMutex property_lock;
+ gchar *delta_link;
+ gint32 version;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CamelM365FolderSummary, camel_m365_folder_summary, CAMEL_TYPE_FOLDER_SUMMARY)
+
+static gboolean
+m365_folder_summary_header_load (CamelFolderSummary *summary,
+ CamelFIRecord *mir)
+{
+ CamelM365FolderSummary *m365_summary = CAMEL_M365_FOLDER_SUMMARY (summary);
+ const gchar *delta_link = NULL;
+ gchar *part;
+
+ if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_m365_folder_summary_parent_class)->summary_header_load
(summary, mir))
+ return FALSE;
+
+ m365_summary->priv->version = 0;
+
+ part = mir->bdata;
+
+ if (part)
+ m365_summary->priv->version = camel_util_bdata_get_number (&part, 0);
+
+ if (part && *part && part[1])
+ delta_link = part + 1;
+
+ /* Do not call camel_m365_folder_summary_set_sync_state() here,
+ to not mark the summary dirty after load. */
+ LOCK (m365_summary);
+
+ if (g_strcmp0 (m365_summary->priv->delta_link, delta_link) != 0) {
+ g_free (m365_summary->priv->delta_link);
+ m365_summary->priv->delta_link = g_strdup (delta_link);
+ }
+
+ UNLOCK (m365_summary);
+
+ return TRUE;
+}
+
+static CamelFIRecord *
+m365_folder_summary_header_save (CamelFolderSummary *summary,
+ GError **error)
+{
+ CamelM365FolderSummary *m365_summary = CAMEL_M365_FOLDER_SUMMARY (summary);
+ CamelFIRecord *fir;
+ gchar *delta_link;
+
+ fir = CAMEL_FOLDER_SUMMARY_CLASS (camel_m365_folder_summary_parent_class)->summary_header_save
(summary, error);
+
+ if (!fir)
+ return NULL;
+
+ delta_link = camel_m365_folder_summary_dup_delta_link (m365_summary);
+
+ fir->bdata = g_strdup_printf ("%d %s", CAMEL_M365_FOLDER_SUMMARY_VERSION, delta_link ? delta_link :
"");
+
+ g_free (delta_link);
+
+ LOCK (m365_summary);
+
+ m365_summary->priv->version = CAMEL_M365_FOLDER_SUMMARY_VERSION;
+
+ UNLOCK (m365_summary);
+
+ return fir;
+}
+
+static void
+m365_folder_summary_finalize (GObject *object)
+{
+ CamelM365FolderSummary *m365_summary = CAMEL_M365_FOLDER_SUMMARY (object);
+
+ g_free (m365_summary->priv->delta_link);
+ g_mutex_clear (&m365_summary->priv->property_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_folder_summary_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_folder_summary_class_init (CamelM365FolderSummaryClass *class)
+{
+ CamelFolderSummaryClass *folder_summary_class;
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = m365_folder_summary_finalize;
+
+ folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
+ folder_summary_class->message_info_type = CAMEL_TYPE_M365_MESSAGE_INFO;
+ folder_summary_class->summary_header_save = m365_folder_summary_header_save;
+ folder_summary_class->summary_header_load = m365_folder_summary_header_load;
+}
+
+static void
+camel_m365_folder_summary_init (CamelM365FolderSummary *m365_summary)
+{
+ m365_summary->priv = camel_m365_folder_summary_get_instance_private (m365_summary);
+
+ g_mutex_init (&m365_summary->priv->property_lock);
+}
+
+CamelFolderSummary *
+camel_m365_folder_summary_new (struct _CamelFolder *folder)
+{
+ CamelFolderSummary *summary;
+
+ summary = g_object_new (CAMEL_TYPE_M365_FOLDER_SUMMARY, "folder", folder, NULL);
+
+ camel_folder_summary_load (summary, NULL);
+
+ return summary;
+}
+
+gint
+camel_m365_folder_summary_get_version (CamelM365FolderSummary *m365_summary)
+{
+ gint version;
+
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER_SUMMARY (m365_summary), -1);
+
+ LOCK (m365_summary);
+
+ version = m365_summary->priv->version;
+
+ UNLOCK (m365_summary);
+
+ return version;
+}
+
+void
+camel_m365_folder_summary_set_delta_link (CamelM365FolderSummary *m365_summary,
+ const gchar *delta_link)
+{
+ gboolean changed = FALSE;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER_SUMMARY (m365_summary));
+
+ LOCK (m365_summary);
+
+ if (g_strcmp0 (m365_summary->priv->delta_link, delta_link) != 0) {
+ gchar *dup = g_strdup (delta_link);
+
+ g_free (m365_summary->priv->delta_link);
+ m365_summary->priv->delta_link = dup;
+
+ changed = TRUE;
+ }
+
+ UNLOCK (m365_summary);
+
+ if (changed)
+ camel_folder_summary_touch (CAMEL_FOLDER_SUMMARY (m365_summary));
+}
+
+gchar *
+camel_m365_folder_summary_dup_delta_link (CamelM365FolderSummary *m365_summary)
+{
+ gchar *delta_link;
+
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER_SUMMARY (m365_summary), NULL);
+
+ LOCK (m365_summary);
+
+ delta_link = g_strdup (m365_summary->priv->delta_link);
+
+ UNLOCK (m365_summary);
+
+ return delta_link;
+}
+
+void
+camel_m365_folder_summary_clear (CamelFolderSummary *summary)
+{
+ CamelFolderChangeInfo *changes;
+ GPtrArray *known_uids;
+ gint i;
+
+ changes = camel_folder_change_info_new ();
+ known_uids = camel_folder_summary_get_array (summary);
+
+ for (i = 0; i < known_uids->len; i++) {
+ const gchar *uid = g_ptr_array_index (known_uids, i);
+
+ if (!uid)
+ continue;
+
+ camel_folder_change_info_remove_uid (changes, uid);
+ }
+
+ camel_folder_summary_clear (summary, NULL);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (camel_folder_summary_get_folder (summary), changes);
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_free_array (known_uids);
+}
+
+static gboolean
+m365_folder_summary_update_user_flags (CamelMessageInfo *info,
+ const CamelNamedFlags *server_user_flags)
+{
+ gboolean changed = FALSE;
+ gboolean set_cal = FALSE, set_note = FALSE;
+
+ if (camel_message_info_get_user_flag (info, "$has_cal"))
+ set_cal = TRUE;
+ if (camel_message_info_get_user_flag (info, "$has_note"))
+ set_note = TRUE;
+
+ changed = camel_message_info_take_user_flags (info, camel_named_flags_copy (server_user_flags));
+
+ /* reset the flags as they were set in messageinfo before */
+ if (set_cal)
+ camel_message_info_set_user_flag (info, "$has_cal", TRUE);
+ if (set_note)
+ camel_message_info_set_user_flag (info, "$has_note", TRUE);
+
+ return changed;
+}
+
+gboolean
+camel_m365_folder_summary_update_message_info_flags (CamelFolderSummary *summary,
+ CamelMessageInfo *info,
+ guint32 server_flags,
+ const CamelNamedFlags *server_user_flags)
+{
+ CamelM365MessageInfo *omi;
+ gboolean changed = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER_SUMMARY (summary), FALSE);
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (info), FALSE);
+
+ omi = CAMEL_M365_MESSAGE_INFO (info);
+
+ if (server_flags != camel_m365_message_info_get_server_flags (omi)) {
+ guint32 server_set, server_cleared;
+
+ server_set = server_flags & ~camel_m365_message_info_get_server_flags (omi);
+ server_cleared = camel_m365_message_info_get_server_flags (omi) & ~server_flags;
+
+ camel_message_info_set_flags (info, server_set | server_cleared,
(camel_message_info_get_flags (info) | server_set) & ~server_cleared);
+ camel_m365_message_info_set_server_flags (omi, server_flags);
+ changed = TRUE;
+ }
+
+ if (server_user_flags && m365_folder_summary_update_user_flags (info, server_user_flags))
+ changed = TRUE;
+
+ return changed;
+}
+
+gboolean
+camel_m365_folder_summary_add_message (CamelFolderSummary *summary,
+ const gchar *uid,
+ const gchar *change_key,
+ CamelMessageInfo *info,
+ CamelMimeMessage *message)
+{
+ CamelMessageInfo *mi;
+
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (info != NULL, FALSE);
+ g_return_val_if_fail (message != NULL, FALSE);
+
+ mi = camel_folder_summary_info_new_from_message (summary, message);
+ g_return_val_if_fail (mi != NULL, FALSE);
+
+ camel_message_info_set_abort_notifications (mi, TRUE);
+
+ camel_m365_message_info_set_change_key (CAMEL_M365_MESSAGE_INFO (mi), change_key);
+ camel_message_info_set_flags (mi, ~0, camel_message_info_get_flags (info));
+ camel_message_info_take_user_flags (mi, camel_message_info_dup_user_flags (info));
+ camel_message_info_take_user_tags (mi, camel_message_info_dup_user_tags (info));
+ camel_message_info_set_size (mi, camel_message_info_get_size (info));
+ camel_message_info_set_uid (mi, uid);
+
+ camel_message_info_set_abort_notifications (mi, FALSE);
+
+ camel_folder_summary_add (summary, mi, FALSE);
+
+ g_object_unref (mi);
+
+ return TRUE;
+}
diff --git a/src/Microsoft365/camel/camel-m365-folder-summary.h
b/src/Microsoft365/camel/camel-m365-folder-summary.h
new file mode 100644
index 00000000..086a20ab
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-folder-summary.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_FOLDER_SUMMARY_H
+#define CAMEL_M365_FOLDER_SUMMARY_H
+
+#include <camel/camel.h>
+
+#include "camel-m365-message-info.h"
+
+#define CAMEL_M365_FOLDER_SUMMARY_VERSION (1)
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_FOLDER_SUMMARY \
+ (camel_m365_folder_summary_get_type ())
+#define CAMEL_M365_FOLDER_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_FOLDER_SUMMARY, CamelM365FolderSummary))
+#define CAMEL_M365_FOLDER_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_FOLDER_SUMMARY, CamelM365FolderSummaryClass))
+#define CAMEL_IS_M365_FOLDER_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_FOLDER_SUMMARY))
+#define CAMEL_IS_M365_FOLDER_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_FOLDER_SUMMARY))
+#define CAMEL_M365_FOLDER_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_FOLDER_SUMMARY, CamelM365FolderSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelM365FolderSummary CamelM365FolderSummary;
+typedef struct _CamelM365FolderSummaryClass CamelM365FolderSummaryClass;
+typedef struct _CamelM365FolderSummaryPrivate CamelM365FolderSummaryPrivate;
+
+struct _CamelM365FolderSummary {
+ CamelFolderSummary parent;
+ CamelM365FolderSummaryPrivate *priv;
+};
+
+struct _CamelM365FolderSummaryClass {
+ CamelFolderSummaryClass parent_class;
+};
+
+GType camel_m365_folder_summary_get_type (void);
+
+CamelFolderSummary *
+ camel_m365_folder_summary_new (CamelFolder *folder);
+gint camel_m365_folder_summary_get_version (CamelM365FolderSummary *m365_summary);
+void camel_m365_folder_summary_set_delta_link(CamelM365FolderSummary *m365_summary,
+ const gchar *delta_link);
+gchar * camel_m365_folder_summary_dup_delta_link(CamelM365FolderSummary *m365_summary);
+void camel_m365_folder_summary_clear (CamelFolderSummary *summary);
+gboolean camel_m365_folder_summary_update_message_info_flags
+ (CamelFolderSummary *summary,
+ CamelMessageInfo *info,
+ guint32 server_flags,
+ const CamelNamedFlags *server_user_flags);
+gboolean camel_m365_folder_summary_add_message (CamelFolderSummary *summary,
+ const gchar *uid,
+ const gchar *change_key,
+ CamelMessageInfo *info,
+ CamelMimeMessage *message);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_FOLDER_SUMMARY_H */
diff --git a/src/Microsoft365/camel/camel-m365-folder.c b/src/Microsoft365/camel/camel-m365-folder.c
new file mode 100644
index 00000000..8cb54599
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-folder.c
@@ -0,0 +1,1847 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+
+#include "camel-m365-folder-summary.h"
+#include "camel-m365-store.h"
+#include "camel-m365-store-summary.h"
+#include "camel-m365-utils.h"
+
+#include "camel-m365-folder.h"
+
+#define M365_LOCAL_CACHE_PATH "cur"
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 */
+#define M365_FETCH_SUMMARY_PROPERTIES "categories," \
+ "ccRecipients," \
+ "changeKey," \
+ "flag," \
+ "from," \
+ "hasAttachments," \
+ "id," \
+ "importance," \
+ "internetMessageHeaders," \
+ "internetMessageId," \
+ "isRead," \
+ "receivedDateTime," \
+ "sender," \
+ "sentDateTime," \
+ "subject," \
+ "toRecipients"
+
+#define LOCK_CACHE(_folder) g_rec_mutex_lock (&_folder->priv->cache_lock)
+#define UNLOCK_CACHE(_folder) g_rec_mutex_unlock (&_folder->priv->cache_lock)
+
+#define LOCK_SEARCH(_folder) g_mutex_lock (&_folder->priv->search_lock)
+#define UNLOCK_SEARCH(_folder) g_mutex_unlock (&_folder->priv->search_lock)
+
+struct _CamelM365FolderPrivate {
+ gchar *id; /* folder ID; stays the same for the full life of the folder */
+
+ GRecMutex cache_lock;
+ CamelDataCache *cache;
+
+ GMutex search_lock;
+ CamelFolderSearch *search;
+
+ /* To not download the same message multiple times from different threads */
+ GMutex get_message_lock;
+ GCond get_message_cond;
+ GHashTable *get_message_hash; /* borrowed gchar *uid ~> NULL */
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CamelM365Folder, camel_m365_folder, CAMEL_TYPE_OFFLINE_FOLDER)
+
+static GChecksum *
+m365_folder_cache_new_checksum (const gchar *id)
+{
+ GChecksum *checksum;
+
+ g_return_val_if_fail (id != NULL, NULL);
+
+ /* No need to use SHA here, the string is short, will not collide with the others */
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+
+ g_checksum_update (checksum, (const guchar *) id, strlen (id));
+
+ return checksum;
+}
+
+static CamelStream *
+m365_folder_cache_add (CamelM365Folder *m365_folder,
+ const gchar *id,
+ GError **error)
+{
+ GIOStream *base_stream;
+ CamelStream *stream = NULL;
+ GChecksum *checksum;
+
+ checksum = m365_folder_cache_new_checksum (id);
+
+ LOCK_CACHE (m365_folder);
+ base_stream = camel_data_cache_add (m365_folder->priv->cache, M365_LOCAL_CACHE_PATH,
g_checksum_get_string (checksum), error);
+ UNLOCK_CACHE (m365_folder);
+
+ g_checksum_free (checksum);
+
+ if (base_stream) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ }
+
+ return stream;
+}
+
+static gint
+m365_folder_cache_remove (CamelM365Folder *m365_folder,
+ const gchar *id,
+ GError **error)
+{
+ GChecksum *checksum;
+ gint ret;
+
+ checksum = m365_folder_cache_new_checksum (id);
+
+ LOCK_CACHE (m365_folder);
+ ret = camel_data_cache_remove (m365_folder->priv->cache, M365_LOCAL_CACHE_PATH, g_checksum_get_string
(checksum), error);
+ UNLOCK_CACHE (m365_folder);
+
+ g_checksum_free (checksum);
+
+ return ret;
+}
+
+static CamelStream *
+m365_folder_cache_get (CamelM365Folder *m365_folder,
+ const gchar *id,
+ GError **error)
+{
+ GChecksum *checksum;
+ CamelStream *stream = NULL;
+ GIOStream *base_stream;
+
+ checksum = m365_folder_cache_new_checksum (id);
+
+ LOCK_CACHE (m365_folder);
+ base_stream = camel_data_cache_get (m365_folder->priv->cache, M365_LOCAL_CACHE_PATH,
g_checksum_get_string (checksum), error);
+ UNLOCK_CACHE (m365_folder);
+
+ g_checksum_free (checksum);
+
+ if (base_stream) {
+ stream = camel_stream_new (base_stream);
+ g_object_unref (base_stream);
+ }
+
+ return stream;
+}
+
+static gchar *
+m365_folder_cache_dup_filename (CamelM365Folder *m365_folder,
+ const gchar *id)
+{
+ GChecksum *checksum;
+ gchar *filename;
+
+ checksum = m365_folder_cache_new_checksum (id);
+
+ LOCK_CACHE (m365_folder);
+ filename = camel_data_cache_get_filename (m365_folder->priv->cache, M365_LOCAL_CACHE_PATH,
g_checksum_get_string (checksum));
+ UNLOCK_CACHE (m365_folder);
+
+ g_checksum_free (checksum);
+
+ return filename;
+}
+
+static CamelMimeMessage *
+m365_folder_get_message_from_cache (CamelM365Folder *m365_folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *stream;
+ CamelMimeMessage *msg;
+
+ stream = m365_folder_cache_get (m365_folder, uid, error);
+
+ if (!stream)
+ return NULL;
+
+ msg = camel_mime_message_new ();
+
+ if (!camel_data_wrapper_construct_from_stream_sync (CAMEL_DATA_WRAPPER (msg), stream, cancellable,
error))
+ g_clear_object (&msg);
+
+ g_object_unref (stream);
+
+ return msg;
+}
+
+static void
+m365_folder_save_summary (CamelM365Folder *m365_folder)
+{
+ CamelFolderSummary *summary;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (m365_folder));
+
+ summary = camel_folder_get_folder_summary (CAMEL_FOLDER (m365_folder));
+
+ if (summary) {
+ GError *error = NULL;
+
+ if (!camel_folder_summary_save (summary, &error))
+ g_warning ("%s: Failed to save summary: %s", G_STRFUNC, error ? error->message :
"Unknown error");
+
+ g_clear_error (&error);
+ }
+}
+
+static void
+m365_folder_forget_all_mails (CamelM365Folder *m365_folder)
+{
+ CamelFolder *folder;
+ CamelFolderChangeInfo *changes;
+ CamelFolderSummary *folder_summary;
+ GPtrArray *known_uids;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (m365_folder));
+
+ folder = CAMEL_FOLDER (m365_folder);
+ g_return_if_fail (folder != NULL);
+
+ known_uids = camel_folder_summary_get_array (camel_folder_get_folder_summary (folder));
+
+ if (!known_uids)
+ return;
+
+ changes = camel_folder_change_info_new ();
+ folder_summary = camel_folder_get_folder_summary (folder);
+
+ camel_folder_summary_lock (folder_summary);
+
+ for (ii = 0; ii < known_uids->len; ii++) {
+ const gchar *uid = g_ptr_array_index (known_uids, ii);
+
+ camel_folder_change_info_remove_uid (changes, uid);
+ m365_folder_cache_remove (m365_folder, uid, NULL);
+ }
+
+ camel_folder_summary_clear (folder_summary, NULL);
+ camel_folder_summary_unlock (folder_summary);
+
+ m365_folder_save_summary (m365_folder);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (folder, changes);
+
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_free_array (known_uids);
+}
+
+static guint32
+m365_folder_get_permanent_flags (CamelFolder *folder)
+{
+ return CAMEL_MESSAGE_ANSWERED |
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_DRAFT |
+ CAMEL_MESSAGE_FLAGGED |
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_FORWARDED |
+ CAMEL_MESSAGE_USER;
+}
+
+static CamelMimeMessage *
+m365_folder_get_message_cached (CamelFolder *folder,
+ const gchar *message_uid,
+ GCancellable *cancellable)
+{
+ return m365_folder_get_message_from_cache (CAMEL_M365_FOLDER (folder), message_uid, cancellable,
NULL);
+}
+
+static void
+m365_folder_exec_search (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GPtrArray **out_matches,
+ guint32 *out_count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Folder *m365_folder;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (folder));
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+
+ LOCK_SEARCH (m365_folder);
+
+ camel_folder_search_set_folder (m365_folder->priv->search, folder);
+
+ if (out_matches)
+ *out_matches = camel_folder_search_search (m365_folder->priv->search, expression, uids,
cancellable, error);
+
+ if (out_count)
+ *out_count = camel_folder_search_count (m365_folder->priv->search, expression, cancellable,
error);
+
+ UNLOCK_SEARCH (m365_folder);
+}
+
+static GPtrArray *
+m365_folder_search_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *matches = NULL;
+
+ m365_folder_exec_search (folder, expression, NULL, &matches, NULL, cancellable, error);
+
+ return matches;
+}
+
+static guint32
+m365_folder_count_by_expression (CamelFolder *folder,
+ const gchar *expression,
+ GCancellable *cancellable,
+ GError **error)
+{
+ guint32 count = 0;
+
+ m365_folder_exec_search (folder, expression, NULL, NULL, &count, cancellable, error);
+
+ return count;
+}
+
+static GPtrArray *
+m365_folder_search_by_uids (CamelFolder *folder,
+ const gchar *expression,
+ GPtrArray *uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *matches = NULL;
+
+ if (uids->len == 0)
+ return g_ptr_array_new ();
+
+ m365_folder_exec_search (folder, expression, uids, &matches, NULL, cancellable, error);
+
+ return matches;
+}
+
+static void
+m365_folder_search_free (CamelFolder *folder,
+ GPtrArray *uids)
+{
+ CamelM365Folder *m365_folder;
+
+ if (!uids)
+ return;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (folder));
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+
+ LOCK_SEARCH (m365_folder);
+
+ camel_folder_search_free_result (m365_folder->priv->search, uids);
+
+ UNLOCK_SEARCH (m365_folder);
+}
+
+static gint
+m365_folder_cmp_uids (CamelFolder *folder,
+ const gchar *uid1,
+ const gchar *uid2)
+{
+ g_return_val_if_fail (uid1 != NULL, 0);
+ g_return_val_if_fail (uid2 != NULL, 0);
+
+ return strcmp (uid1, uid2);
+}
+
+static gboolean
+m365_folder_download_message_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *raw_data_stream,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStream *cache_stream = user_data;
+ gssize expected_size = 0, wrote_size = 0, last_percent = -1;
+ gint last_progress_notify = 0;
+ gsize buffer_size = 65535;
+ gchar *buffer;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (cache_stream), FALSE);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (raw_data_stream), FALSE);
+
+ if (message && message->response_headers) {
+ const gchar *content_length_str;
+
+ content_length_str = soup_message_headers_get_one (message->response_headers,
"Content-Length");
+
+ if (content_length_str && *content_length_str)
+ expected_size = (gssize) g_ascii_strtoll (content_length_str, NULL, 10);
+ }
+
+ buffer = g_malloc (buffer_size);
+
+ do {
+ success = !g_cancellable_set_error_if_cancelled (cancellable, error);
+
+ if (success) {
+ gssize n_read, n_wrote;
+
+ n_read = g_input_stream_read (raw_data_stream, buffer, buffer_size, cancellable,
error);
+
+ if (n_read == -1) {
+ success = FALSE;
+ } else if (!n_read) {
+ break;
+ } else {
+ n_wrote = camel_stream_write (cache_stream, buffer, n_read, cancellable,
error);
+ success = n_read == n_wrote;
+
+ if (success && expected_size > 0) {
+ gssize percent;
+
+ wrote_size += n_wrote;
+
+ percent = wrote_size * 100.0 / expected_size;
+
+ if (percent > 100)
+ percent = 100;
+
+ if (percent != last_percent) {
+ gint64 now = g_get_monotonic_time ();
+
+ /* Notify only 10 times per second, not more */
+ if (percent == 100 || now - last_progress_notify >
G_USEC_PER_SEC / 10) {
+ last_progress_notify = now;
+ last_percent = percent;
+
+ camel_operation_progress (cancellable, percent);
+ }
+ }
+ }
+ }
+ }
+ } while (success);
+
+ g_free (buffer);
+
+ if (success)
+ camel_stream_flush (cache_stream, cancellable, NULL);
+
+ return success;
+}
+
+static void
+m365_folder_get_message_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ CamelM365Folder *m365_folder = user_data;
+
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (m365_folder));
+
+ g_mutex_lock (&m365_folder->priv->get_message_lock);
+ g_cond_broadcast (&m365_folder->priv->get_message_cond);
+ g_mutex_unlock (&m365_folder->priv->get_message_lock);
+}
+
+static CamelMimeMessage *
+m365_folder_get_message_sync (CamelFolder *folder,
+ const gchar *uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelMimeMessage *message = NULL;
+ CamelM365Folder *m365_folder;
+ CamelM365Store *m365_store;
+ CamelStore *parent_store;
+ CamelStream *cache_stream = NULL;
+ EM365Connection *cnc = NULL;
+ GError *local_error = NULL;
+ const gchar *folder_id;
+ gboolean success = TRUE, remove_from_hash = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER (folder), NULL);
+ g_return_val_if_fail (uid != NULL, NULL);
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (!parent_store) {
+ g_set_error_literal (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_STATE,
+ _("Invalid folder state (missing parent store)"));
+ return NULL;
+ }
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return NULL;
+
+ folder_id = camel_m365_folder_get_id (m365_folder);
+
+ g_mutex_lock (&m365_folder->priv->get_message_lock);
+
+ if (g_hash_table_contains (m365_folder->priv->get_message_hash, uid)) {
+ gulong handler_id = 0;
+
+ if (cancellable) {
+ handler_id = g_signal_connect (cancellable, "cancelled",
+ G_CALLBACK (m365_folder_get_message_cancelled_cb), m365_folder);
+ }
+
+ while (success = !g_cancellable_set_error_if_cancelled (cancellable, error),
+ success && g_hash_table_contains (m365_folder->priv->get_message_hash, uid)) {
+ g_cond_wait (&m365_folder->priv->get_message_cond,
&m365_folder->priv->get_message_lock);
+ }
+
+ if (success)
+ message = m365_folder_get_message_from_cache (m365_folder, uid, cancellable, NULL);
+
+ if (handler_id)
+ g_signal_handler_disconnect (cancellable, handler_id);
+ }
+
+ if (success && !message) {
+ g_hash_table_insert (m365_folder->priv->get_message_hash, (gpointer) uid, NULL);
+ remove_from_hash = TRUE;
+ }
+
+ g_mutex_unlock (&m365_folder->priv->get_message_lock);
+
+ if (success && !message) {
+ cache_stream = m365_folder_cache_add (m365_folder, uid, error);
+
+ success = cache_stream != NULL;
+
+ success = success && e_m365_connection_get_mail_message_sync (cnc, NULL, folder_id, uid,
+ m365_folder_download_message_cb, cache_stream, cancellable, &local_error);
+
+ if (local_error) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+
+ g_propagate_error (error, local_error);
+ success = FALSE;
+ }
+
+ if (success) {
+ /* First free the cache stream, thus the follwing call opens a new instance,
+ which is rewinded at the beginning of the stream. */
+ g_clear_object (&cache_stream);
+
+ message = m365_folder_get_message_from_cache (m365_folder, uid, cancellable, error);
+ }
+ }
+
+ g_clear_object (&cache_stream);
+ g_clear_object (&cnc);
+
+ if (remove_from_hash) {
+ g_mutex_lock (&m365_folder->priv->get_message_lock);
+ g_hash_table_remove (m365_folder->priv->get_message_hash, uid);
+ g_cond_broadcast (&m365_folder->priv->get_message_cond);
+ g_mutex_unlock (&m365_folder->priv->get_message_lock);
+ }
+
+ return message;
+}
+
+static gboolean
+m365_folder_append_message_sync (CamelFolder *folder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **appended_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Cannot put existing messages from other providers, because:
+ 1) those are always set as drafts
+ 2) the set sentDateTime property is not respected
+ 3) internetMessageHeaders is limited to 5! headers only:
+ {
+ "error": {
+ "code": "InvalidInternetMessageHeaderCollection",
+ "message": "Maximum number of headers in one message should be less than or equal to
5.",
+ "innerError": {
+ "date": "2020-07-01T10:03:34",
+ "request-id": "a46da0ea-8933-43c6-932d-7c751f226516"
+ }
+ }
+ }
+ 4) there are likely to be more limitations on the graph API, not spotted yet.
+
+ There is opened a feture request, which may eventually fix this, but it's currently not done yet
(as of 2020-07-01):
+
https://microsoftgraph.uservoice.com/forums/920506-microsoft-graph-feature-requests/suggestions/35049175-put-edit-mime-email-content-with-microsoft-graph
+
+ Thus just error out for now.
+ */
+
+#ifdef ENABLE_MAINTAINER_MODE /* Only for easier testing */
+ CamelStore *parent_store;
+ CamelM365Store *m365_store;
+ EM365Connection *cnc = NULL;
+ gboolean success;
+ GError *local_error = NULL;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (!CAMEL_IS_M365_STORE (parent_store)) {
+ g_set_error_literal (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_STATE,
+ _("Invalid folder state (missing parent store)"));
+ return FALSE;
+ }
+
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ success = camel_m365_utils_create_message_sync (cnc, camel_m365_folder_get_id (CAMEL_M365_FOLDER
(folder)),
+ message, info, appended_uid, cancellable, &local_error);
+
+ g_clear_object (&cnc);
+
+ if (!success)
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+#else
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Cannot add messages into an Microsoft 365 account from another account. Only messages from
the same account can be moved/copied between the Microsoft 365 folders."));
+ return FALSE;
+#endif
+}
+
+static gboolean
+m365_folder_merge_server_user_flags (CamelMessageInfo *mi,
+ EM365MailMessage *mail)
+{
+ CamelFolderSummary *summary;
+ JsonArray *categories;
+ GHashTable *current_labels;
+ const CamelNamedFlags *user_flags;
+ guint ii, len;
+ gboolean changed = FALSE;
+
+ summary = camel_message_info_ref_summary (mi);
+ if (summary)
+ camel_folder_summary_lock (summary);
+ camel_message_info_property_lock (mi);
+ camel_message_info_freeze_notifications (mi);
+
+ current_labels = g_hash_table_new (g_str_hash, g_str_equal);
+
+ user_flags = camel_message_info_get_user_flags (mi);
+ len = camel_named_flags_get_length (user_flags);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = camel_named_flags_get (user_flags, ii);
+
+ if (!camel_m365_utils_is_system_user_flag (name))
+ g_hash_table_insert (current_labels, (gpointer) name, NULL);
+ }
+
+ categories = e_m365_mail_message_get_categories (mail);
+
+ if (categories) {
+ len = json_array_get_length (categories);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = json_array_get_string_element (categories, ii);
+
+ name = camel_m365_utils_rename_label (name, TRUE);
+
+ if (name && *name) {
+ gchar *flag;
+
+ flag = camel_m365_utils_encode_category_name (name);
+
+ if (!g_hash_table_remove (current_labels, flag)) {
+ changed = TRUE;
+
+ camel_message_info_set_user_flag (mi, flag, TRUE);
+ }
+
+ g_free (flag);
+ }
+ }
+ }
+
+ /* Those left here are to be removed */
+ if (g_hash_table_size (current_labels)) {
+ GHashTableIter iter;
+ gpointer key;
+
+ changed = TRUE;
+
+ g_hash_table_iter_init (&iter, current_labels);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ camel_message_info_set_user_flag (mi, key, FALSE);
+ }
+ }
+
+ camel_message_info_thaw_notifications (mi);
+ camel_message_info_property_unlock (mi);
+ if (summary)
+ camel_folder_summary_unlock (summary);
+ g_clear_object (&summary);
+
+ return changed;
+}
+
+static gboolean
+m365_folder_update_message_info (CamelMessageInfo *mi,
+ EM365MailMessage *mail)
+{
+ CamelM365MessageInfo *m365_mi;
+ guint32 flags = 0;
+ gboolean changed = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (mi), FALSE);
+ g_return_val_if_fail (mail != NULL, FALSE);
+
+ m365_mi = CAMEL_M365_MESSAGE_INFO (mi);
+
+ if (e_m365_mail_message_get_has_attachments (mail))
+ flags |= CAMEL_MESSAGE_ATTACHMENTS;
+
+ if (e_m365_mail_message_get_is_draft (mail))
+ flags |= CAMEL_MESSAGE_DRAFT;
+
+ if (e_m365_mail_message_get_is_read (mail))
+ flags |= CAMEL_MESSAGE_SEEN;
+
+ if (e_m365_mail_message_get_importance (mail) == E_M365_IMPORTANCE_HIGH)
+ flags |= CAMEL_MESSAGE_FLAGGED;
+
+ /* 2020-06-24 - cannot make it work, even with
https://stackoverflow.com/questions/58205494/access-the-replied-forwarded-etc-state-from-rest */
+ /* CAMEL_MESSAGE_ANSWERED
+ CAMEL_MESSAGE_FORWARDED */
+
+ if (camel_m365_message_info_set_server_flags (m365_mi, flags)) {
+ guint32 mask;
+
+ mask = CAMEL_MESSAGE_ATTACHMENTS | CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN |
CAMEL_MESSAGE_FLAGGED;
+
+ camel_message_info_set_flags (mi, mask, flags);
+
+ changed = TRUE;
+ }
+
+ changed = m365_folder_merge_server_user_flags (mi, mail) || changed;
+
+ return changed;
+}
+
+static gchar *
+m365_folder_recipients_as_string (JsonArray *recipients) /* EM365Recipient * */
+{
+ CamelInternetAddress *addrs;
+ guint ii, len;
+ gchar *res;
+
+ if (!recipients)
+ return NULL;
+
+ addrs = camel_internet_address_new ();
+
+ len = json_array_get_length (recipients);
+ for (ii = 0; ii < len; ii++) {
+ EM365Recipient *recipient = json_array_get_object_element (recipients, ii);
+ const gchar *name, *address;
+
+ name = e_m365_recipient_get_name (recipient);
+ address = e_m365_recipient_get_address (recipient);
+
+ if (address && *address)
+ camel_internet_address_add (addrs, name, address);
+ }
+
+ if (camel_address_length (CAMEL_ADDRESS (addrs)) > 0) {
+ res = camel_address_format (CAMEL_ADDRESS (addrs));
+ } else {
+ res = NULL;
+ }
+
+ g_clear_object (&addrs);
+
+ return res;
+}
+
+static CamelMessageInfo *
+m365_folder_new_message_info_from_mail_message (CamelFolder *folder,
+ EM365MailMessage *mail)
+{
+ CamelMessageInfo *mi = NULL;
+ CamelNameValueArray *headers = NULL;
+ JsonArray *json_headers;
+ EM365Recipient *from;
+ const gchar *ctmp;
+ time_t tt;
+ gchar *tmp;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
+ g_return_val_if_fail (mail != NULL, NULL);
+
+ json_headers = e_m365_mail_message_get_internet_message_headers (mail);
+
+ if (json_headers && json_array_get_length (json_headers) > 0) {
+ guint ii, len = json_array_get_length (json_headers);
+
+ headers = camel_name_value_array_new_sized (len);
+
+ for (ii = 0; ii < len; ii++) {
+ EM365InternetMessageHeader *header = json_array_get_object_element (json_headers, ii);
+ const gchar *name, *value;
+
+ name = e_m365_internet_message_header_get_name (header);
+ value = e_m365_internet_message_header_get_value (header);
+
+ if (name && *name)
+ camel_name_value_array_append (headers, name, value ? value : "");
+ }
+
+ if (camel_name_value_array_get_length (headers)) {
+ mi = camel_message_info_new_from_headers (camel_folder_get_folder_summary (folder),
headers);
+ } else {
+ camel_name_value_array_free (headers);
+ headers = NULL;
+ }
+ }
+
+ if (!mi)
+ mi = camel_message_info_new (camel_folder_get_folder_summary (folder));
+
+ camel_message_info_set_abort_notifications (mi, TRUE);
+
+ ctmp = e_m365_mail_message_get_subject (mail);
+
+ if (ctmp)
+ camel_message_info_set_subject (mi, ctmp);
+
+ from = e_m365_mail_message_get_from (mail);
+
+ if (from) {
+ const gchar *name, *address;
+
+ name = e_m365_recipient_get_name (from);
+ address = e_m365_recipient_get_address (from);
+
+ if (address && *address) {
+ tmp = camel_internet_address_format_address (name, address);
+
+ if (tmp) {
+ camel_message_info_set_from (mi, tmp);
+
+ g_free (tmp);
+ }
+ }
+ }
+
+ tmp = m365_folder_recipients_as_string (e_m365_mail_message_get_to_recipients (mail));
+
+ if (tmp) {
+ camel_message_info_set_to (mi, tmp);
+ g_free (tmp);
+ }
+
+ tmp = m365_folder_recipients_as_string (e_m365_mail_message_get_cc_recipients (mail));
+
+ if (tmp) {
+ camel_message_info_set_cc (mi, tmp);
+ g_free (tmp);
+ }
+
+ tt = e_m365_mail_message_get_sent_date_time (mail);
+
+ if (tt)
+ camel_message_info_set_date_sent (mi, (gint64) tt);
+
+ tt = e_m365_mail_message_get_received_date_time (mail);
+
+ if (tt)
+ camel_message_info_set_date_received (mi, (gint64) tt);
+
+ ctmp = e_m365_mail_message_get_internet_message_id (mail);
+
+ if (ctmp && *ctmp) {
+ GChecksum *checksum;
+ CamelSummaryMessageID message_id;
+ guint8 *digest;
+ gsize length;
+
+ length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+ digest = g_alloca (length);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) ctmp, -1);
+ g_checksum_get_digest (checksum, digest, &length);
+ g_checksum_free (checksum);
+
+ memcpy (message_id.id.hash, digest, sizeof (message_id.id.hash));
+
+ camel_message_info_set_message_id (mi, message_id.id.id);
+ }
+
+ camel_message_info_set_uid (mi, e_m365_mail_message_get_id (mail));
+
+ if (headers)
+ camel_message_info_take_headers (mi, headers);
+
+ camel_message_info_set_abort_notifications (mi, FALSE);
+
+ m365_folder_update_message_info (mi, mail);
+
+ return mi;
+}
+
+typedef struct _SummaryDeltaData {
+ CamelFolder *folder;
+ CamelFolderChangeInfo *changes;
+ GList *removed_uids; /* gchar * - from the Camel string pool */
+} SummaryDeltaData;
+
+static gboolean
+m365_folder_got_summary_messages_cb (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned objects from the
server */
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SummaryDeltaData *sdd = user_data;
+ CamelFolderSummary *summary;
+ GSList *link;
+
+ g_return_val_if_fail (sdd != NULL, FALSE);
+
+ summary = camel_folder_get_folder_summary (sdd->folder);
+
+ if (!summary)
+ return FALSE;
+
+ for (link = (GSList *) results; link; link = g_slist_next (link)) {
+ EM365MailMessage *mail = link->data;
+ const gchar *id;
+
+ id = e_m365_mail_message_get_id (mail);
+
+ if (!id)
+ continue;
+
+ if (!sdd->changes)
+ sdd->changes = camel_folder_change_info_new ();
+
+ if (e_m365_delta_is_removed_object (mail)) {
+ sdd->removed_uids = g_list_prepend (sdd->removed_uids, (gpointer)
camel_pstring_strdup (id));
+
+ camel_folder_change_info_remove_uid (sdd->changes, id);
+ } else {
+ CamelMessageInfo *info;
+
+ info = camel_folder_summary_get (summary, id);
+
+ if (info) {
+ if (m365_folder_update_message_info (info, mail))
+ camel_folder_change_info_change_uid (sdd->changes, id);
+
+ g_object_unref (info);
+ } else {
+ info = m365_folder_new_message_info_from_mail_message (sdd->folder, mail);
+
+ if (info) {
+ camel_folder_summary_add (summary, info, TRUE);
+
+ /* Unset folder-flagged flag, which ahd been set by the
camel_folder_summary_add(),
+ to avoid re-sync on the just added message. */
+ camel_message_info_set_folder_flagged (info, FALSE);
+
+ camel_folder_change_info_add_uid (sdd->changes, id);
+ camel_folder_change_info_recent_uid (sdd->changes, id);
+
+ g_object_unref (info);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+m365_folder_refresh_info_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Folder *m365_folder;
+ CamelM365FolderSummary *m365_folder_summary;
+ CamelM365Store *m365_store;
+ CamelFolderSummary *folder_summary;
+ CamelStore *parent_store;
+ EM365Connection *cnc = NULL;
+ SummaryDeltaData sdd;
+ GError *local_error = NULL;
+ const gchar *folder_id;
+ gchar *curr_delta_link, *new_delta_link = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER (folder), FALSE);
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (!parent_store) {
+ g_set_error_literal (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_STATE,
+ _("Invalid folder state (missing parent store)"));
+ return FALSE;
+ }
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ folder_id = camel_m365_folder_get_id (m365_folder);
+ folder_summary = camel_folder_get_folder_summary (folder);
+ m365_folder_summary = CAMEL_M365_FOLDER_SUMMARY (folder_summary);
+
+ curr_delta_link = camel_m365_folder_summary_dup_delta_link (m365_folder_summary);
+
+ sdd.folder = folder;
+ sdd.changes = NULL;
+ sdd.removed_uids = NULL;
+
+ success = e_m365_connection_get_objects_delta_sync (cnc, NULL, E_M365_FOLDER_KIND_MAIL, folder_id,
M365_FETCH_SUMMARY_PROPERTIES,
+ curr_delta_link, 0, m365_folder_got_summary_messages_cb, &sdd,
+ &new_delta_link, cancellable, &local_error);
+
+ if (curr_delta_link && e_m365_connection_util_delta_token_failed (local_error)) {
+ g_clear_error (&local_error);
+ g_clear_pointer (&curr_delta_link, g_free);
+
+ camel_m365_folder_summary_set_delta_link (m365_folder_summary, NULL);
+
+ m365_folder_forget_all_mails (m365_folder);
+
+ success = e_m365_connection_get_objects_delta_sync (cnc, NULL, E_M365_FOLDER_KIND_MAIL,
folder_id, M365_FETCH_SUMMARY_PROPERTIES,
+ NULL, 0, m365_folder_got_summary_messages_cb, &sdd,
+ &new_delta_link, cancellable, &local_error);
+ }
+
+ if (success && new_delta_link)
+ camel_m365_folder_summary_set_delta_link (m365_folder_summary, new_delta_link);
+
+ if (sdd.removed_uids) {
+ camel_folder_summary_remove_uids (folder_summary, sdd.removed_uids);
+
+ g_list_free_full (sdd.removed_uids, (GDestroyNotify) camel_pstring_free);
+ }
+
+ m365_folder_save_summary (m365_folder);
+
+ if (sdd.changes) {
+ if (camel_folder_change_info_changed (sdd.changes))
+ camel_folder_changed (folder, sdd.changes);
+
+ camel_folder_change_info_free (sdd.changes);
+ }
+
+ if (local_error) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+
+ g_propagate_error (error, local_error);
+ success = FALSE;
+ }
+
+ g_clear_object (&cnc);
+ g_free (curr_delta_link);
+ g_free (new_delta_link);
+
+ return success;
+}
+
+static gboolean
+m365_folder_copy_move_to_folder_sync (CamelFolder *folder,
+ CamelM365Store *m365_store,
+ const GSList *uids,
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365StoreSummary *m365_store_summary;
+ EM365Connection *cnc = NULL;
+ GSList *des_ids = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (des_folder_id != NULL, FALSE);
+
+ m365_store_summary = camel_m365_store_ref_store_summary (m365_store);
+
+ if (g_strcmp0 (des_folder_id, "junkemail") == 0) {
+ des_folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store_summary,
CAMEL_FOLDER_TYPE_JUNK);
+ } else if (g_strcmp0 (des_folder_id, "deleteditems") == 0) {
+ des_folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store_summary,
CAMEL_FOLDER_TYPE_TRASH);
+ } else if (g_strcmp0 (des_folder_id, "inbox") == 0) {
+ des_folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store_summary,
CAMEL_FOLDER_TYPE_INBOX);
+ }
+
+ g_clear_object (&m365_store_summary);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ success = e_m365_connection_copy_move_mail_messages_sync (cnc, NULL, uids, des_folder_id, do_copy,
+ &des_ids, cancellable, error);
+
+ g_clear_object (&cnc);
+
+ if (!do_copy) {
+ CamelFolderChangeInfo *src_changes;
+ CamelM365Folder *m365_folder;
+ GSList *des_link, *src_link;
+ GList *removed_uids = NULL;
+
+ src_changes = camel_folder_change_info_new ();
+ m365_folder = CAMEL_M365_FOLDER (folder);
+
+ camel_folder_lock (folder);
+
+ /* Can succeed partially, thus always check the moved ids */
+ for (src_link = (GSList *) uids, des_link = des_ids;
+ src_link && des_link;
+ src_link = g_slist_next (src_link), des_link = g_slist_next (des_link)) {
+ const gchar *src_uid = src_link->data;
+
+ m365_folder_cache_remove (m365_folder, src_uid, NULL);
+
+ removed_uids = g_list_prepend (removed_uids, (gpointer) src_uid);
+ camel_folder_change_info_remove_uid (src_changes, src_uid);
+ }
+
+ if (removed_uids) {
+ CamelFolderSummary *summary;
+
+ summary = camel_folder_get_folder_summary (folder);
+ camel_folder_summary_remove_uids (summary, removed_uids);
+
+ g_list_free (removed_uids);
+ }
+
+ if (camel_folder_change_info_changed (src_changes))
+ camel_folder_changed (folder, src_changes);
+
+ camel_folder_change_info_free (src_changes);
+
+ camel_folder_unlock (folder);
+ }
+
+ g_slist_free_full (des_ids, (GDestroyNotify) camel_pstring_free);
+
+ return success;
+}
+
+static gboolean
+m365_folder_delete_messages_sync (CamelFolder *folder,
+ CamelM365Store *m365_store,
+ const GSList *uids,
+ gboolean is_trash_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365Connection *cnc = NULL;
+ gboolean success;
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ if (is_trash_folder) {
+ GSList *deleted_uids = NULL, *link;
+
+ success = e_m365_connection_delete_mail_messages_sync (cnc, NULL, uids, &deleted_uids,
cancellable, error);
+
+ if (deleted_uids) {
+ CamelFolderChangeInfo *changes;
+ CamelM365Folder *m365_folder;
+ GList *removed_uids = NULL;
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+ changes = camel_folder_change_info_new ();
+
+ camel_folder_lock (folder);
+
+ /* Can succeed partially, thus always check the moved ids */
+ for (link = deleted_uids; link; link = g_slist_next (link)) {
+ const gchar *uid = link->data;
+
+ m365_folder_cache_remove (m365_folder, uid, NULL);
+
+ removed_uids = g_list_prepend (removed_uids, (gpointer) uid);
+ camel_folder_change_info_remove_uid (changes, uid);
+ }
+
+ if (removed_uids) {
+ CamelFolderSummary *summary;
+
+ summary = camel_folder_get_folder_summary (folder);
+ camel_folder_summary_remove_uids (summary, removed_uids);
+
+ g_list_free (removed_uids);
+ }
+
+ if (camel_folder_change_info_changed (changes))
+ camel_folder_changed (folder, changes);
+
+ camel_folder_change_info_free (changes);
+
+ camel_folder_unlock (folder);
+
+ g_slist_free (deleted_uids);
+ }
+ } else {
+ success = m365_folder_copy_move_to_folder_sync (folder, m365_store,
+ uids, "deleteditems", FALSE, cancellable, error);
+ }
+
+ g_clear_object (&cnc);
+
+ return success;
+}
+
+static JsonBuilder *
+m365_folder_message_info_changes_to_json (CamelMessageInfo *mi)
+{
+ JsonBuilder *builder;
+
+ builder = json_builder_new_immutable ();
+ e_m365_json_begin_object_member (builder, NULL);
+
+ camel_m365_utils_add_message_flags (builder, mi, NULL);
+
+ e_m365_json_end_object_member (builder);
+
+ return builder;
+}
+
+static gboolean
+m365_folder_save_flags_sync (CamelFolder *folder,
+ CamelM365Store *m365_store,
+ GSList *mi_list, /* CamelMessageInfo * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365Connection *cnc = NULL;
+ gboolean success = TRUE;
+
+ /* Trap an error, but do not stop other processing */
+ g_return_val_if_fail (mi_list != NULL, TRUE);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ if (mi_list->next) {
+ GSList *link;
+ GPtrArray *requests;
+
+ requests = g_ptr_array_new_full (g_slist_length (mi_list), g_object_unref);
+
+ for (link = mi_list; link && success; link = g_slist_next (link)) {
+ CamelMessageInfo *mi = link->data;
+ SoupMessage *message;
+ JsonBuilder *builder;
+
+ builder = m365_folder_message_info_changes_to_json (mi);
+
+ message = e_m365_connection_prepare_update_mail_message (cnc, NULL,
+ camel_message_info_get_uid (mi), builder, error);
+
+ g_clear_object (&builder);
+
+ if (!message)
+ success = FALSE;
+ else
+ g_ptr_array_add (requests, message);
+ }
+
+ if (success)
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0, requests,
cancellable, error);
+
+ g_ptr_array_free (requests, TRUE);
+ } else {
+ CamelMessageInfo *mi = mi_list->data;
+ JsonBuilder *builder;
+
+ builder = m365_folder_message_info_changes_to_json (mi);
+
+ success = e_m365_connection_update_mail_message_sync (cnc, NULL,
+ camel_message_info_get_uid (mi), builder, cancellable, error);
+
+ g_clear_object (&builder);
+ }
+
+ g_object_unref (cnc);
+
+ if (success) {
+ GSList *link;
+
+ camel_folder_lock (folder);
+
+ for (link = mi_list; link; link = g_slist_next (link)) {
+ CamelMessageInfo *mi = link->data;
+
+ camel_message_info_set_folder_flagged (mi, FALSE);
+ }
+
+ camel_folder_unlock (folder);
+ }
+
+ return success;
+}
+
+static gboolean
+m365_folder_is_of_type (CamelFolder *folder,
+ guint32 folder_type)
+{
+ CamelStore *parent_store;
+ CamelM365Store *m365_store;
+ CamelM365StoreSummary *m365_store_summary;
+ gboolean is_of_type;
+ const gchar *folder_id;
+
+ g_return_val_if_fail (folder != NULL, FALSE);
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (!parent_store)
+ return FALSE;
+
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ g_return_val_if_fail (m365_store != NULL, FALSE);
+
+ m365_store_summary = camel_m365_store_ref_store_summary (m365_store);
+
+ folder_type = folder_type & CAMEL_FOLDER_TYPE_MASK;
+ folder_id = camel_m365_folder_get_id (CAMEL_M365_FOLDER (folder));
+ is_of_type = folder_id &&
+ (camel_m365_store_summary_get_folder_flags (m365_store_summary, folder_id) &
CAMEL_FOLDER_TYPE_MASK) == folder_type;
+
+ g_clear_object (&m365_store_summary);
+
+ return is_of_type;
+}
+
+static gboolean
+m365_folder_synchronize_sync (CamelFolder *folder,
+ gboolean expunge,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelStore *parent_store;
+ CamelFolderSummary *folder_summary;
+ GPtrArray *uids;
+ GSList *mi_list = NULL, *deleted_uids = NULL, *junk_uids = NULL, *inbox_uids = NULL;
+ gint mi_list_len = 0;
+ gboolean is_junk_folder;
+ gboolean success = TRUE;
+ guint ii;
+ GError *local_error = NULL;
+
+ parent_store = camel_folder_get_parent_store (folder);
+
+ if (!parent_store) {
+ g_set_error_literal (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_STATE,
+ _("Invalid folder state (missing parent store)"));
+ return FALSE;
+ }
+
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ if (!camel_m365_store_ensure_connected (m365_store, NULL, cancellable, error))
+ return FALSE;
+
+ folder_summary = camel_folder_get_folder_summary (folder);
+
+ if (camel_folder_summary_get_deleted_count (folder_summary) > 0 ||
+ camel_folder_summary_get_junk_count (folder_summary) > 0) {
+ camel_folder_summary_prepare_fetch_all (folder_summary, NULL);
+ uids = camel_folder_summary_get_array (folder_summary);
+ } else {
+ uids = camel_folder_summary_get_changed (folder_summary);
+ }
+
+ if (!uids || !uids->len) {
+ camel_folder_summary_free_array (uids);
+ return TRUE;
+ }
+
+ is_junk_folder = m365_folder_is_of_type (folder, CAMEL_FOLDER_TYPE_JUNK);
+
+ for (ii = 0; success && ii < uids->len; ii++) {
+ guint32 flags_changed, flags_set;
+ CamelMessageInfo *mi;
+ const gchar *uid;
+
+ uid = uids->pdata[ii];
+ mi = camel_folder_summary_get (folder_summary, uid);
+
+ if (!mi)
+ continue;
+
+ flags_set = camel_message_info_get_flags (mi);
+ flags_changed = camel_m365_message_info_get_server_flags (CAMEL_M365_MESSAGE_INFO (mi)) ^
flags_set;
+
+ if ((flags_set & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0 &&
+ (flags_changed & (CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_FORWARDED |
CAMEL_MESSAGE_FLAGGED)) != 0) {
+ mi_list = g_slist_prepend (mi_list, mi);
+ mi_list_len++;
+
+ if (flags_set & CAMEL_MESSAGE_DELETED)
+ deleted_uids = g_slist_prepend (deleted_uids, (gpointer) camel_pstring_strdup
(uid));
+ else if (flags_set & CAMEL_MESSAGE_JUNK)
+ junk_uids = g_slist_prepend (junk_uids, (gpointer) camel_pstring_strdup
(uid));
+ else if (is_junk_folder && (flags_set & CAMEL_MESSAGE_NOTJUNK) != 0)
+ inbox_uids = g_slist_prepend (inbox_uids, (gpointer) camel_pstring_strdup
(uid));
+ } else if (flags_set & CAMEL_MESSAGE_DELETED) {
+ deleted_uids = g_slist_prepend (deleted_uids, (gpointer) camel_pstring_strdup (uid));
+ g_clear_object (&mi);
+ } else if (flags_set & CAMEL_MESSAGE_JUNK) {
+ junk_uids = g_slist_prepend (junk_uids, (gpointer) camel_pstring_strdup (uid));
+ g_clear_object (&mi);
+ } else if (is_junk_folder && (flags_set & CAMEL_MESSAGE_NOTJUNK) != 0) {
+ inbox_uids = g_slist_prepend (inbox_uids, (gpointer) camel_pstring_strdup (uid));
+ g_clear_object (&mi);
+ } else if ((flags_set & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0) {
+ /* OK, the change must have been the labels */
+ mi_list = g_slist_prepend (mi_list, mi);
+ mi_list_len++;
+ } else {
+ g_clear_object (&mi);
+ }
+
+ if (mi_list_len == E_M365_BATCH_MAX_REQUESTS) {
+ success = m365_folder_save_flags_sync (folder, m365_store, mi_list, cancellable,
&local_error);
+ g_slist_free_full (mi_list, g_object_unref);
+ mi_list = NULL;
+ mi_list_len = 0;
+ }
+ }
+
+ if (mi_list != NULL && success)
+ success = m365_folder_save_flags_sync (folder, m365_store, mi_list, cancellable,
&local_error);
+ g_slist_free_full (mi_list, g_object_unref);
+
+ if (deleted_uids && success)
+ success = m365_folder_delete_messages_sync (folder, m365_store, deleted_uids,
m365_folder_is_of_type (folder, CAMEL_FOLDER_TYPE_TRASH), cancellable, &local_error);
+ g_slist_free_full (deleted_uids, (GDestroyNotify) camel_pstring_free);
+
+ if (junk_uids && success)
+ success = m365_folder_copy_move_to_folder_sync (folder, m365_store, junk_uids, "junkemail",
FALSE, cancellable, &local_error);
+ g_slist_free_full (junk_uids, (GDestroyNotify) camel_pstring_free);
+
+ if (inbox_uids && success)
+ success = m365_folder_copy_move_to_folder_sync (folder, m365_store, inbox_uids, "inbox",
FALSE, cancellable, &local_error);
+ g_slist_free_full (inbox_uids, (GDestroyNotify) camel_pstring_free);
+
+ camel_folder_summary_save (folder_summary, NULL);
+ camel_folder_summary_free_array (uids);
+
+ if (local_error) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ return success;
+}
+
+static gboolean
+m365_folder_expunge_sync (CamelFolder *folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* it does nothing special here, everything is done as part of the m365_folder_synchronize_sync() */
+
+ return TRUE;
+}
+
+static gboolean
+m365_folder_transfer_messages_to_sync (CamelFolder *source,
+ GPtrArray *uids,
+ CamelFolder *destination,
+ gboolean delete_originals,
+ GPtrArray **transferred_uids,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelStore *parent_store;
+ CamelM365Store *m365_store;
+ GSList *uids_list = NULL;
+ gboolean success;
+ guint ii;
+ GError *local_error = NULL;
+
+ /* The parent class ensures this, but recheck anyway, for completeness */
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER (source), FALSE);
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER (destination), FALSE);
+ g_return_val_if_fail (uids != NULL, FALSE);
+
+ parent_store = camel_folder_get_parent_store (source);
+
+ if (!parent_store) {
+ g_set_error_literal (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_STATE,
+ _("Invalid folder state (missing parent store)"));
+ return FALSE;
+ }
+
+ /* The parent class ensures this, but recheck anyway, for completeness */
+ g_return_val_if_fail (camel_folder_get_parent_store (destination) == parent_store, FALSE);
+
+ m365_store = CAMEL_M365_STORE (parent_store);
+
+ if (!camel_m365_store_ensure_connected (m365_store, NULL, cancellable, error))
+ return FALSE;
+
+ for (ii = 0; ii < uids->len; ii++) {
+ uids_list = g_slist_prepend (uids_list, g_ptr_array_index (uids, ii));
+ }
+
+ uids_list = g_slist_reverse (uids_list);
+
+ success = m365_folder_copy_move_to_folder_sync (source, m365_store,
+ uids_list, camel_m365_folder_get_id (CAMEL_M365_FOLDER (destination)),
+ !delete_originals, cancellable, &local_error);
+
+ g_slist_free (uids_list);
+
+ /* Update destination folder only if not frozen, to not update
+ for each single message transfer during filtering.
+ */
+ if (success && !camel_folder_is_frozen (destination)) {
+ camel_operation_progress (cancellable, -1);
+
+ m365_folder_refresh_info_sync (destination, cancellable, NULL);
+ }
+
+ if (local_error) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ return success;
+}
+
+static void
+m365_folder_prepare_content_refresh (CamelFolder *folder)
+{
+ g_return_if_fail (CAMEL_IS_M365_FOLDER (folder));
+
+ camel_m365_folder_summary_set_delta_link (CAMEL_M365_FOLDER_SUMMARY (camel_folder_get_folder_summary
(folder)), NULL);
+}
+
+static gchar *
+m365_folder_get_filename (CamelFolder *folder,
+ const gchar *uid,
+ GError **error)
+{
+ CamelM365Folder *m365_folder = CAMEL_M365_FOLDER (folder);
+
+ return m365_folder_cache_dup_filename (m365_folder, uid);
+}
+
+static void
+m365_folder_constructed (GObject *object)
+{
+ CamelSettings *settings;
+ CamelStore *parent_store;
+ CamelService *service;
+ CamelFolder *folder;
+ const gchar *full_name;
+ gchar *description;
+ gchar *user;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_folder_parent_class)->constructed (object);
+
+ folder = CAMEL_FOLDER (object);
+ full_name = camel_folder_get_full_name (folder);
+ parent_store = camel_folder_get_parent_store (folder);
+
+ service = CAMEL_SERVICE (parent_store);
+
+ settings = camel_service_ref_settings (service);
+ user = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (settings));
+
+ g_object_unref (settings);
+
+ description = g_strdup_printf ("%s@Microsoft365:%s", user, full_name);
+ camel_folder_set_description (folder, description);
+ g_free (description);
+
+ g_free (user);
+}
+
+static void
+m365_folder_dispose (GObject *object)
+{
+ CamelM365Folder *m365_folder = CAMEL_M365_FOLDER (object);
+ CamelFolderSummary *summary;
+
+ summary = camel_folder_get_folder_summary (CAMEL_FOLDER (m365_folder));
+
+ if (summary)
+ m365_folder_save_summary (m365_folder);
+
+ LOCK_CACHE (m365_folder);
+ g_clear_object (&m365_folder->priv->cache);
+ UNLOCK_CACHE (m365_folder);
+
+ LOCK_SEARCH (m365_folder);
+ g_clear_object (&m365_folder->priv->search);
+ UNLOCK_SEARCH (m365_folder);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_folder_parent_class)->dispose (object);
+}
+
+static void
+m365_folder_finalize (GObject *object)
+{
+ CamelM365Folder *m365_folder = CAMEL_M365_FOLDER (object);
+
+ g_rec_mutex_clear (&m365_folder->priv->cache_lock);
+ g_mutex_clear (&m365_folder->priv->search_lock);
+ g_mutex_clear (&m365_folder->priv->get_message_lock);
+ g_cond_clear (&m365_folder->priv->get_message_cond);
+
+ g_hash_table_destroy (m365_folder->priv->get_message_hash);
+
+ g_clear_pointer (&m365_folder->priv->id, g_free);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_folder_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_folder_class_init (CamelM365FolderClass *klass)
+{
+ GObjectClass *object_class;
+ CamelFolderClass *folder_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = m365_folder_constructed;
+ object_class->dispose = m365_folder_dispose;
+ object_class->finalize = m365_folder_finalize;
+
+ folder_class = CAMEL_FOLDER_CLASS (klass);
+ folder_class->get_permanent_flags = m365_folder_get_permanent_flags;
+ folder_class->get_message_cached = m365_folder_get_message_cached;
+ folder_class->search_by_expression = m365_folder_search_by_expression;
+ folder_class->count_by_expression = m365_folder_count_by_expression;
+ folder_class->search_by_uids = m365_folder_search_by_uids;
+ folder_class->search_free = m365_folder_search_free;
+ folder_class->cmp_uids = m365_folder_cmp_uids;
+ folder_class->append_message_sync = m365_folder_append_message_sync;
+ folder_class->get_message_sync = m365_folder_get_message_sync;
+ folder_class->refresh_info_sync = m365_folder_refresh_info_sync;
+ folder_class->synchronize_sync = m365_folder_synchronize_sync;
+ folder_class->expunge_sync = m365_folder_expunge_sync;
+ folder_class->transfer_messages_to_sync = m365_folder_transfer_messages_to_sync;
+ folder_class->prepare_content_refresh = m365_folder_prepare_content_refresh;
+ folder_class->get_filename = m365_folder_get_filename;
+}
+
+static void
+camel_m365_folder_init (CamelM365Folder *m365_folder)
+{
+ CamelFolder *folder = CAMEL_FOLDER (m365_folder);
+
+ m365_folder->priv = camel_m365_folder_get_instance_private (m365_folder);
+
+ g_rec_mutex_init (&m365_folder->priv->cache_lock);
+ g_mutex_init (&m365_folder->priv->search_lock);
+ g_mutex_init (&m365_folder->priv->get_message_lock);
+ g_cond_init (&m365_folder->priv->get_message_cond);
+
+ m365_folder->priv->get_message_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ camel_folder_set_flags (folder, CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY);
+ camel_folder_set_lock_async (folder, TRUE);
+}
+
+static gboolean
+m365_folder_has_inbox_type (CamelM365Store *m365_store,
+ const gchar *full_name)
+{
+ CamelM365StoreSummary *summary;
+ guint32 flags;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), FALSE);
+ g_return_val_if_fail (full_name != NULL, FALSE);
+
+ summary = camel_m365_store_ref_store_summary (m365_store);
+
+ if (!summary)
+ return FALSE;
+
+ flags = camel_m365_store_summary_get_folder_flags_for_full_name (summary, full_name);
+
+ g_object_unref (summary);
+
+ return (flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX;
+}
+
+CamelFolder *
+camel_m365_folder_new (CamelStore *store,
+ const gchar *display_name,
+ const gchar *full_name,
+ const gchar *folder_dir,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelFolder *folder;
+ CamelFolderSummary *folder_summary;
+ CamelM365Folder *m365_folder;
+ CamelM365StoreSummary *m365_store_summary;
+ CamelSettings *settings;
+ gboolean filter_inbox = FALSE;
+ gboolean filter_junk = FALSE;
+ gboolean filter_junk_inbox = FALSE;
+ gboolean offline_limit_by_age = FALSE;
+ CamelTimeUnit offline_limit_unit;
+ gint offline_limit_value = 0;
+ guint32 add_folder_flags = 0;
+ gchar *state_file;
+ gchar *folder_id;
+
+ m365_store_summary = camel_m365_store_ref_store_summary (CAMEL_M365_STORE (store));
+ folder_id = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store_summary, full_name);
+ g_clear_object (&m365_store_summary);
+
+ if (!folder_id) {
+ g_set_error (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_PATH,
+ _("Folder ā%sā doesn't correspond to any known folder"), full_name);
+ return NULL;
+ }
+
+ folder = g_object_new (CAMEL_TYPE_M365_FOLDER,
+ "display_name", display_name,
+ "full-name", full_name,
+ "parent_store", store,
+ NULL);
+
+ m365_folder = CAMEL_M365_FOLDER (folder);
+ m365_folder->priv->id = folder_id;
+
+ folder_summary = camel_m365_folder_summary_new (folder);
+
+ if (!folder_summary) {
+ g_object_unref (folder);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Could not load summary for ā%sā"), full_name);
+ return NULL;
+ }
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ g_object_get (
+ settings,
+ "filter-inbox", &filter_inbox,
+ "filter-junk", &filter_junk,
+ "filter-junk-inbox", &filter_junk_inbox,
+ "limit-by-age", &offline_limit_by_age,
+ "limit-unit", &offline_limit_unit,
+ "limit-value", &offline_limit_value,
+ NULL);
+
+ g_clear_object (&settings);
+
+ camel_folder_take_folder_summary (folder, folder_summary);
+
+ /* set/load persistent state */
+ state_file = g_build_filename (folder_dir, "cmeta", NULL);
+ camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
+ camel_object_state_read (CAMEL_OBJECT (folder));
+ g_free (state_file);
+
+ m365_folder->priv->cache = camel_data_cache_new (folder_dir, error);
+ if (!m365_folder->priv->cache) {
+ g_object_unref (folder);
+ return NULL;
+ }
+
+ if (camel_offline_folder_can_downsync (CAMEL_OFFLINE_FOLDER (folder))) {
+ time_t when = (time_t) 0;
+
+ if (offline_limit_by_age)
+ when = camel_time_value_apply (when, offline_limit_unit, offline_limit_value);
+
+ if (when <= (time_t) 0)
+ when = (time_t) -1;
+
+ /* Ensure cache will expire when set up, otherwise
+ * it causes redownload of messages too soon. */
+ camel_data_cache_set_expire_age (m365_folder->priv->cache, when);
+ camel_data_cache_set_expire_access (m365_folder->priv->cache, when);
+ } else {
+ /* Set cache expiration for one week. */
+ camel_data_cache_set_expire_age (m365_folder->priv->cache, 60 * 60 * 24 * 7);
+ camel_data_cache_set_expire_access (m365_folder->priv->cache, 60 * 60 * 24 * 7);
+ }
+
+ camel_binding_bind_property (store, "online",
+ m365_folder->priv->cache, "expire-enabled",
+ G_BINDING_SYNC_CREATE);
+
+ if (m365_folder_has_inbox_type (CAMEL_M365_STORE (store), full_name)) {
+ if (filter_inbox)
+ add_folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ if (filter_junk)
+ add_folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+ } else {
+ if (filter_junk && !filter_junk_inbox)
+ add_folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+ }
+
+ if (add_folder_flags)
+ camel_folder_set_flags (folder, camel_folder_get_flags (folder) | add_folder_flags);
+
+ camel_m365_store_connect_folder_summary (CAMEL_M365_STORE (store), folder_summary);
+
+ m365_folder->priv->search = camel_folder_search_new ();
+
+ return folder;
+}
+
+const gchar *
+camel_m365_folder_get_id (CamelM365Folder *m365_folder)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_FOLDER (m365_folder), NULL);
+
+ return m365_folder->priv->id;
+}
diff --git a/src/Microsoft365/camel/camel-m365-folder.h b/src/Microsoft365/camel/camel-m365-folder.h
new file mode 100644
index 00000000..c2463839
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-folder.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_FOLDER_H
+#define CAMEL_M365_FOLDER_H
+
+#include <camel/camel.h>
+
+#include "camel-m365-folder-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_FOLDER \
+ (camel_m365_folder_get_type ())
+#define CAMEL_M365_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_FOLDER, CamelM365Folder))
+#define CAMEL_M365_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_FOLDER, CamelM365FolderClass))
+#define CAMEL_IS_M365_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_FOLDER))
+#define CAMEL_IS_M365_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_FOLDER))
+#define CAMEL_M365_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_FOLDER, CamelM365FolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelM365Folder CamelM365Folder;
+typedef struct _CamelM365FolderClass CamelM365FolderClass;
+typedef struct _CamelM365FolderPrivate CamelM365FolderPrivate;
+
+struct _CamelM365Folder {
+ CamelOfflineFolder parent;
+ CamelM365FolderPrivate *priv;
+};
+
+struct _CamelM365FolderClass {
+ CamelOfflineFolderClass parent_class;
+};
+
+GType camel_m365_folder_get_type (void);
+
+CamelFolder * camel_m365_folder_new (CamelStore *store,
+ const gchar *display_name,
+ const gchar *full_name,
+ const gchar *folder_dir,
+ GCancellable *cancellable,
+ GError **error);
+const gchar * camel_m365_folder_get_id (CamelM365Folder *m365_folder);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_FOLDER_H */
diff --git a/src/Microsoft365/camel/camel-m365-message-info.c
b/src/Microsoft365/camel/camel-m365-message-info.c
new file mode 100644
index 00000000..453ef492
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-message-info.c
@@ -0,0 +1,409 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <stdio.h>
+
+#include "camel/camel.h"
+
+#include "camel-m365-message-info.h"
+
+struct _CamelM365MessageInfoPrivate {
+ guint32 server_flags;
+ gint32 item_type;
+ gchar *change_key;
+};
+
+enum {
+ PROP_0,
+ PROP_SERVER_FLAGS,
+ PROP_ITEM_TYPE,
+ PROP_CHANGE_KEY,
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CamelM365MessageInfo, camel_m365_message_info, CAMEL_TYPE_MESSAGE_INFO_BASE)
+
+static CamelMessageInfo *
+m365_message_info_clone (const CamelMessageInfo *mi,
+ CamelFolderSummary *assign_summary)
+{
+ CamelMessageInfo *result;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (mi), NULL);
+
+ result = CAMEL_MESSAGE_INFO_CLASS (camel_m365_message_info_parent_class)->clone (mi, assign_summary);
+ if (!result)
+ return NULL;
+
+ if (CAMEL_IS_M365_MESSAGE_INFO (result)) {
+ CamelM365MessageInfo *omi, *omi_result;
+
+ omi = CAMEL_M365_MESSAGE_INFO (mi);
+ omi_result = CAMEL_M365_MESSAGE_INFO (result);
+
+ camel_m365_message_info_set_server_flags (omi_result,
camel_m365_message_info_get_server_flags (omi));
+ camel_m365_message_info_set_item_type (omi_result, camel_m365_message_info_get_item_type
(omi));
+ camel_m365_message_info_take_change_key (omi_result, camel_m365_message_info_dup_change_key
(omi));
+ }
+
+ return result;
+}
+
+static gboolean
+m365_message_info_load (CamelMessageInfo *mi,
+ const CamelMIRecord *record,
+ /* const */ gchar **bdata_ptr)
+{
+ CamelM365MessageInfo *omi;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (mi), FALSE);
+ g_return_val_if_fail (record != NULL, FALSE);
+ g_return_val_if_fail (bdata_ptr != NULL, FALSE);
+
+ if (!CAMEL_MESSAGE_INFO_CLASS (camel_m365_message_info_parent_class)->load ||
+ !CAMEL_MESSAGE_INFO_CLASS (camel_m365_message_info_parent_class)->load (mi, record, bdata_ptr))
+ return FALSE;
+
+ omi = CAMEL_M365_MESSAGE_INFO (mi);
+
+ if (*bdata_ptr) {
+ gchar **values;
+
+ values = g_strsplit (*bdata_ptr, " ", -1);
+
+ if (values && values[0] && values[1] && values[2]) {
+ camel_m365_message_info_set_server_flags (omi, g_ascii_strtoll (values[0], NULL, 10));
+ camel_m365_message_info_set_item_type (omi, g_ascii_strtoll (values[1], NULL, 10));
+ camel_m365_message_info_set_change_key (omi, values[2]);
+ }
+
+ g_strfreev (values);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+m365_message_info_save (const CamelMessageInfo *mi,
+ CamelMIRecord *record,
+ GString *bdata_str)
+{
+ CamelM365MessageInfo *omi;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (mi), FALSE);
+ g_return_val_if_fail (record != NULL, FALSE);
+ g_return_val_if_fail (bdata_str != NULL, FALSE);
+
+ if (!CAMEL_MESSAGE_INFO_CLASS (camel_m365_message_info_parent_class)->save ||
+ !CAMEL_MESSAGE_INFO_CLASS (camel_m365_message_info_parent_class)->save (mi, record, bdata_str))
+ return FALSE;
+
+ omi = CAMEL_M365_MESSAGE_INFO (mi);
+
+ g_string_append_printf (bdata_str, "%u %d %s",
+ camel_m365_message_info_get_server_flags (omi),
+ camel_m365_message_info_get_item_type (omi),
+ camel_m365_message_info_get_change_key (omi));
+
+ return TRUE;
+}
+
+static void
+m365_message_info_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CamelM365MessageInfo *omi = CAMEL_M365_MESSAGE_INFO (object);
+
+ switch (property_id) {
+ case PROP_SERVER_FLAGS:
+ camel_m365_message_info_set_server_flags (omi, g_value_get_uint (value));
+ return;
+
+ case PROP_ITEM_TYPE:
+ camel_m365_message_info_set_item_type (omi, g_value_get_int (value));
+ return;
+
+ case PROP_CHANGE_KEY:
+ camel_m365_message_info_set_change_key (omi, g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_message_info_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CamelM365MessageInfo *omi = CAMEL_M365_MESSAGE_INFO (object);
+
+ switch (property_id) {
+
+ case PROP_SERVER_FLAGS:
+ g_value_set_uint (value, camel_m365_message_info_get_server_flags (omi));
+ return;
+
+ case PROP_ITEM_TYPE:
+ g_value_set_int (value, camel_m365_message_info_get_item_type (omi));
+ return;
+
+ case PROP_CHANGE_KEY:
+ g_value_take_string (value, camel_m365_message_info_dup_change_key (omi));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_message_info_dispose (GObject *object)
+{
+ CamelM365MessageInfo *omi = CAMEL_M365_MESSAGE_INFO (object);
+
+ g_free (omi->priv->change_key);
+ omi->priv->change_key = NULL;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_message_info_parent_class)->dispose (object);
+}
+
+static void
+camel_m365_message_info_class_init (CamelM365MessageInfoClass *class)
+{
+ CamelMessageInfoClass *mi_class;
+ GObjectClass *object_class;
+
+ mi_class = CAMEL_MESSAGE_INFO_CLASS (class);
+ mi_class->clone = m365_message_info_clone;
+ mi_class->load = m365_message_info_load;
+ mi_class->save = m365_message_info_save;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = m365_message_info_set_property;
+ object_class->get_property = m365_message_info_get_property;
+ object_class->dispose = m365_message_info_dispose;
+
+ /*
+ * CamelM365MessageInfo:server-flags
+ *
+ * Last known server flags of the message.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_SERVER_FLAGS,
+ g_param_spec_uint (
+ "server-flags",
+ "Server Flags",
+ NULL,
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READWRITE));
+
+ /*
+ * CamelM365MessageInfo:item-type
+ *
+ * Item type of the message.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_ITEM_TYPE,
+ g_param_spec_int (
+ "item-type",
+ "Item Type",
+ NULL,
+ 0, G_MAXINT32, 0,
+ G_PARAM_READWRITE));
+
+ /*
+ * CamelM365MessageInfo:change-key
+ *
+ * Change key of the message on the server.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CHANGE_KEY,
+ g_param_spec_string (
+ "change-key",
+ "Change Key",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+camel_m365_message_info_init (CamelM365MessageInfo *omi)
+{
+ omi->priv = camel_m365_message_info_get_instance_private (omi);
+}
+
+guint32
+camel_m365_message_info_get_server_flags (const CamelM365MessageInfo *omi)
+{
+ CamelMessageInfo *mi;
+ guint32 result;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), 0);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+ result = omi->priv->server_flags;
+ camel_message_info_property_unlock (mi);
+
+ return result;
+}
+
+gboolean
+camel_m365_message_info_set_server_flags (CamelM365MessageInfo *omi,
+ guint32 server_flags)
+{
+ CamelMessageInfo *mi;
+ gboolean changed;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), FALSE);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+
+ changed = omi->priv->server_flags != server_flags;
+
+ if (changed)
+ omi->priv->server_flags = server_flags;
+
+ camel_message_info_property_unlock (mi);
+
+ if (changed && !camel_message_info_get_abort_notifications (mi)) {
+ g_object_notify (G_OBJECT (omi), "server-flags");
+ camel_message_info_set_dirty (mi, TRUE);
+ }
+
+ return changed;
+}
+
+gint32
+camel_m365_message_info_get_item_type (const CamelM365MessageInfo *omi)
+{
+ CamelMessageInfo *mi;
+ gint32 result;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), 0);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+ result = omi->priv->item_type;
+ camel_message_info_property_unlock (mi);
+
+ return result;
+}
+
+gboolean
+camel_m365_message_info_set_item_type (CamelM365MessageInfo *omi,
+ gint32 item_type)
+{
+ CamelMessageInfo *mi;
+ gboolean changed;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), FALSE);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+
+ changed = omi->priv->item_type != item_type;
+
+ if (changed)
+ omi->priv->item_type = item_type;
+
+ camel_message_info_property_unlock (mi);
+
+ if (changed && !camel_message_info_get_abort_notifications (mi)) {
+ g_object_notify (G_OBJECT (omi), "item-type");
+ camel_message_info_set_dirty (mi, TRUE);
+ }
+
+ return changed;
+}
+
+const gchar *
+camel_m365_message_info_get_change_key (const CamelM365MessageInfo *omi)
+{
+ CamelMessageInfo *mi;
+ const gchar *result;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), NULL);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+ result = omi->priv->change_key;
+ camel_message_info_property_unlock (mi);
+
+ return result;
+}
+
+gchar *
+camel_m365_message_info_dup_change_key (const CamelM365MessageInfo *omi)
+{
+ CamelMessageInfo *mi;
+ gchar *result;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), NULL);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+ result = g_strdup (omi->priv->change_key);
+ camel_message_info_property_unlock (mi);
+
+ return result;
+}
+
+gboolean
+camel_m365_message_info_set_change_key (CamelM365MessageInfo *omi,
+ const gchar *change_key)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), FALSE);
+
+ return camel_m365_message_info_take_change_key (omi, g_strdup (change_key));
+}
+
+gboolean
+camel_m365_message_info_take_change_key (CamelM365MessageInfo *omi,
+ gchar *change_key)
+{
+ CamelMessageInfo *mi;
+ gboolean changed;
+
+ g_return_val_if_fail (CAMEL_IS_M365_MESSAGE_INFO (omi), FALSE);
+
+ mi = CAMEL_MESSAGE_INFO (omi);
+
+ camel_message_info_property_lock (mi);
+
+ changed = g_strcmp0 (omi->priv->change_key, change_key) != 0;
+
+ if (changed) {
+ g_free (omi->priv->change_key);
+ omi->priv->change_key = change_key;
+ } else if (change_key != omi->priv->change_key) {
+ g_free (change_key);
+ }
+
+ camel_message_info_property_unlock (mi);
+
+ if (changed && !camel_message_info_get_abort_notifications (mi)) {
+ g_object_notify (G_OBJECT (omi), "change-key");
+ camel_message_info_set_dirty (mi, TRUE);
+ }
+
+ return changed;
+}
diff --git a/src/Microsoft365/camel/camel-m365-message-info.h
b/src/Microsoft365/camel/camel-m365-message-info.h
new file mode 100644
index 00000000..a0c65dc1
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-message-info.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_MESSAGE_INFO_H
+#define CAMEL_M365_MESSAGE_INFO_H
+
+#include <glib-object.h>
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_MESSAGE_INFO \
+ (camel_m365_message_info_get_type ())
+#define CAMEL_M365_MESSAGE_INFO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_MESSAGE_INFO, CamelM365MessageInfo))
+#define CAMEL_M365_MESSAGE_INFO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_MESSAGE_INFO, CamelM365MessageInfoClass))
+#define CAMEL_IS_M365_MESSAGE_INFO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_MESSAGE_INFO))
+#define CAMEL_IS_M365_MESSAGE_INFO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_MESSAGE_INFO))
+#define CAMEL_M365_MESSAGE_INFO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_MESSAGE_INFO, CamelM365MessageInfoClass))
+
+G_BEGIN_DECLS
+
+/* extra summary flags*/
+enum {
+ CAMEL_M365_MESSAGE_MSGFLAG_RN_PENDING = CAMEL_MESSAGE_FOLDER_FLAGGED << 1
+};
+
+typedef struct _CamelM365MessageInfo CamelM365MessageInfo;
+typedef struct _CamelM365MessageInfoClass CamelM365MessageInfoClass;
+typedef struct _CamelM365MessageInfoPrivate CamelM365MessageInfoPrivate;
+
+struct _CamelM365MessageInfo {
+ CamelMessageInfoBase parent;
+ CamelM365MessageInfoPrivate *priv;
+};
+
+struct _CamelM365MessageInfoClass {
+ CamelMessageInfoBaseClass parent_class;
+};
+
+GType camel_m365_message_info_get_type (void);
+
+guint32 camel_m365_message_info_get_server_flags(const CamelM365MessageInfo *omi);
+gboolean camel_m365_message_info_set_server_flags(CamelM365MessageInfo *omi,
+ guint32 server_flags);
+gint32 camel_m365_message_info_get_item_type (const CamelM365MessageInfo *omi);
+gboolean camel_m365_message_info_set_item_type (CamelM365MessageInfo *omi,
+ gint32 item_type);
+const gchar * camel_m365_message_info_get_change_key (const CamelM365MessageInfo *omi);
+gchar * camel_m365_message_info_dup_change_key (const CamelM365MessageInfo *omi);
+gboolean camel_m365_message_info_set_change_key (CamelM365MessageInfo *omi,
+ const gchar *change_key);
+gboolean camel_m365_message_info_take_change_key (CamelM365MessageInfo *omi,
+ gchar *change_key);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_MESSAGE_INFO_H */
diff --git a/src/Microsoft365/camel/camel-m365-provider.c b/src/Microsoft365/camel/camel-m365-provider.c
new file mode 100644
index 00000000..200ffeaf
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-provider.c
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include "common/camel-sasl-xoauth2-microsoft365.h"
+
+#include "camel-m365-store.h"
+#include "camel-m365-transport.h"
+
+static void add_hash (guint *hash, gchar *s);
+static guint m365_url_hash (gconstpointer key);
+static gint m365_url_equal (gconstpointer a, gconstpointer b);
+
+static CamelProviderConfEntry m365_conf_entries[] = {
+ { CAMEL_PROVIDER_CONF_SECTION_START, "mailcheck", NULL,
+ N_("Checking for new mail") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "check-all", NULL,
+ N_("C_heck for new messages in all folders"), "1" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+
+ { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-inbox", NULL,
+ N_("_Apply filters to new messages in Inbox on this server"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk", NULL,
+ N_("Check new messages for _Junk contents"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk-inbox", "filter-junk",
+ N_("Only check for Junk messages in the IN_BOX folder"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "stay-synchronized", NULL,
+ N_("Synchroni_ze remote mail locally in all folders"), "0" },
+ { CAMEL_PROVIDER_CONF_PLACEHOLDER, "m365-limit-by-age-placeholder", NULL },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+
+ { CAMEL_PROVIDER_CONF_SECTION_START, "connection", NULL, N_("Connection") },
+ { CAMEL_PROVIDER_CONF_CHECKSPIN, "timeout", NULL,
+ /* Translators: '%s' is preplaced with a widget, where "
+ * user can select how long the timeout should be. */
+ N_("Connection _timeout (in seconds) %s"), "0:1:0:32768" },
+ { CAMEL_PROVIDER_CONF_CHECKSPIN, "concurrent-connections", NULL,
+ N_("Numbe_r of concurrent connections to use"), "y:1:1:7" },
+ { CAMEL_PROVIDER_CONF_SECTION_END },
+
+ { CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider m365_provider = {
+ "microsoft365",
+ N_("Microsoft 365"),
+
+ N_("For accessing Microsoft 365 server"),
+
+ "mail",
+
+ CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE |
+ CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_EXTERNAL,
+
+ CAMEL_URL_ALLOW_USER | CAMEL_URL_ALLOW_AUTH | CAMEL_URL_HIDDEN_HOST,
+
+ m365_conf_entries,
+
+ /* ... */
+};
+
+void
+camel_provider_module_init (void)
+{
+ bindtextdomain (GETTEXT_PACKAGE, M365_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ m365_provider.url_hash = m365_url_hash;
+ m365_provider.url_equal = m365_url_equal;
+ m365_provider.authtypes = NULL;
+ m365_provider.translation_domain = GETTEXT_PACKAGE;
+ m365_provider.object_types[CAMEL_PROVIDER_STORE] = CAMEL_TYPE_M365_STORE;
+ m365_provider.object_types[CAMEL_PROVIDER_TRANSPORT] = CAMEL_TYPE_M365_TRANSPORT;
+
+ g_type_ensure (CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365);
+
+ if (g_strcmp0 (g_getenv ("ENABLE_M365"), "1") == 0)
+ camel_provider_register (&m365_provider);
+}
+
+static void
+add_hash (guint *hash,
+ gchar *s)
+{
+ if (s)
+ *hash ^= g_str_hash(s);
+}
+
+static guint
+m365_url_hash (gconstpointer key)
+{
+ const CamelURL *u = (CamelURL *) key;
+ guint hash = 0;
+
+ add_hash (&hash, u->user);
+ add_hash (&hash, u->host);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static gint
+m365_url_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const CamelURL *u1 = a, *u2 = b;
+
+ return ((g_strcmp0 (u1->protocol, u2->protocol) == 0)
+ && (g_strcmp0 (u1->user, u2->user) == 0)
+ && (g_strcmp0 (u1->host, u2->host) == 0)
+ && (u1->port == u2->port));
+}
diff --git a/src/Microsoft365/camel/camel-m365-store-summary.c
b/src/Microsoft365/camel/camel-m365-store-summary.c
new file mode 100644
index 00000000..7b340052
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-store-summary.c
@@ -0,0 +1,1439 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+
+#include "camel-m365-store-summary.h"
+
+#define STORE_GROUP_NAME "##storepriv##"
+#define CATEGORIES_KEY "Categories"
+#define DATA_VERSION 1
+
+#define LOCK(_summary) g_rec_mutex_lock (&(_summary->priv->property_lock))
+#define UNLOCK(_summary) g_rec_mutex_unlock (&(_summary->priv->property_lock))
+
+struct _CamelM365StoreSummaryPrivate {
+ GRecMutex property_lock;
+ gchar *filename;
+ GKeyFile *key_file;
+ GFileMonitor *monitor_delete;
+ gboolean dirty;
+
+ /* Note: We use the *same* strings in both of these hash tables, and
+ * only id_full_name_hash has g_free() hooked up as the destructor func.
+ * So entries must always be removed from full_name_id_hash *first*. */
+ GHashTable *id_full_name_hash; /* id ~> folder full name */
+ GHashTable *full_name_id_hash; /* folder full name ~> id */
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CamelM365StoreSummary, camel_m365_store_summary, G_TYPE_OBJECT)
+
+static gchar *
+m365_store_summary_encode_folder_name (const gchar *display_name)
+{
+ GString *encoded;
+ const gchar *pos;
+
+ if (!display_name || !*display_name)
+ return NULL;
+
+ encoded = g_string_sized_new (strlen (display_name) + 4);
+
+ for (pos = display_name; *pos; pos++) {
+ if (strchr ("%?/", *pos))
+ g_string_append_printf (encoded, "%%%02x", *pos);
+ else
+ g_string_append_c (encoded, *pos);
+ }
+
+ return g_string_free (encoded, FALSE);
+}
+
+#if 0
+static gchar *
+m365_store_summary_decode_folder_name (gchar *pathpart)
+{
+ gchar *pos, *write_pos;
+
+ if (!pathpart || !*pathpart)
+ return pathpart;
+
+ pos = pathpart;
+ write_pos = pathpart;
+
+ while (*pos) {
+ if (*pos == '%' &&
+ g_ascii_isxdigit (pos[1]) &&
+ g_ascii_isxdigit (pos[2])) {
+ *write_pos = (g_ascii_xdigit_value (pos[1]) << 4) + g_ascii_xdigit_value (pos[2]);
+
+ pos += 2;
+ } else if (write_pos != pos) {
+ *write_pos = *pos;
+ }
+
+ pos++;
+ write_pos++;
+ }
+
+ if (write_pos != pos)
+ *write_pos = 0;
+
+ return pathpart;
+}
+#endif
+
+static void
+camel_m365_store_summary_migrate_data_locked (CamelM365StoreSummary *store_summary,
+ gint from_version)
+{
+ /* Here will be any future migration of old data in the summary to new data. */
+
+ g_key_file_set_integer (store_summary->priv->key_file, STORE_GROUP_NAME, "Version", DATA_VERSION);
+}
+
+static void
+m365_store_summary_delete_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event,
+ gpointer user_data)
+{
+ CamelM365StoreSummary *store_summary = user_data;
+
+ if (event == G_FILE_MONITOR_EVENT_DELETED) {
+ LOCK (store_summary);
+
+ if (store_summary->priv->key_file)
+ camel_m365_store_summary_clear (store_summary);
+
+ UNLOCK (store_summary);
+ }
+}
+
+static void
+m365_store_summary_dispose (GObject *object)
+{
+ CamelM365StoreSummary *store_summary = CAMEL_M365_STORE_SUMMARY (object);
+
+ LOCK (store_summary);
+
+ if (store_summary->priv->monitor_delete) {
+ g_signal_handlers_disconnect_by_func (store_summary->priv->monitor_delete,
+ G_CALLBACK (m365_store_summary_delete_cb), store_summary);
+
+ g_clear_object (&store_summary->priv->monitor_delete);
+ }
+
+ UNLOCK (store_summary);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_store_summary_parent_class)->dispose (object);
+}
+
+static void
+m365_store_summary_finalize (GObject *object)
+{
+ CamelM365StoreSummary *store_summary = CAMEL_M365_STORE_SUMMARY (object);
+
+ g_rec_mutex_clear (&store_summary->priv->property_lock);
+ g_hash_table_destroy (store_summary->priv->full_name_id_hash);
+ g_hash_table_destroy (store_summary->priv->id_full_name_hash);
+ g_key_file_free (store_summary->priv->key_file);
+ g_free (store_summary->priv->filename);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_store_summary_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_store_summary_class_init (CamelM365StoreSummaryClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = m365_store_summary_dispose;
+ object_class->finalize = m365_store_summary_finalize;
+}
+
+static void
+camel_m365_store_summary_init (CamelM365StoreSummary *store_summary)
+{
+ store_summary->priv = camel_m365_store_summary_get_instance_private (store_summary);
+ store_summary->priv->key_file = g_key_file_new ();
+ store_summary->priv->id_full_name_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
g_free);
+ store_summary->priv->full_name_id_hash = g_hash_table_new (g_str_hash, g_str_equal); /* shared data
with 'id_full_name_hash' */
+
+ g_rec_mutex_init (&store_summary->priv->property_lock);
+}
+
+CamelM365StoreSummary *
+camel_m365_store_summary_new (const gchar *filename)
+{
+ CamelM365StoreSummary *store_summary;
+ GError *error = NULL;
+ GFile *file;
+
+ g_return_val_if_fail (filename != NULL, NULL);
+
+ file = g_file_new_for_path (filename);
+
+ store_summary = g_object_new (CAMEL_TYPE_M365_STORE_SUMMARY, NULL);
+ store_summary->priv->filename = g_strdup (filename);
+ store_summary->priv->monitor_delete = g_file_monitor_file (file, G_FILE_MONITOR_SEND_MOVED, NULL,
&error);
+
+ if (!error) {
+ g_signal_connect (
+ store_summary->priv->monitor_delete, "changed",
+ G_CALLBACK (m365_store_summary_delete_cb), store_summary);
+ } else {
+ g_warning ("%s: Failed to create monitor_delete: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (file);
+
+ return store_summary;
+}
+
+gboolean
+camel_m365_store_summary_load (CamelM365StoreSummary *store_summary,
+ GError **error)
+{
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+
+ LOCK (store_summary);
+
+ g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+ g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
+
+ store_summary->priv->dirty = FALSE;
+
+ success = g_key_file_load_from_file (store_summary->priv->key_file, store_summary->priv->filename,
G_KEY_FILE_NONE, &local_error);
+
+ if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+ g_key_file_set_integer (store_summary->priv->key_file, STORE_GROUP_NAME, "Version",
DATA_VERSION);
+
+ g_clear_error (&local_error);
+ success = TRUE;
+ } else if (local_error) {
+ g_propagate_error (error, local_error);
+ } else {
+ gint version;
+
+ version = g_key_file_get_integer (store_summary->priv->key_file, STORE_GROUP_NAME, "Version",
NULL);
+
+ if (version && version < DATA_VERSION)
+ camel_m365_store_summary_migrate_data_locked (store_summary, version);
+
+ camel_m365_store_summary_rebuild_hashes (store_summary);
+ }
+
+ UNLOCK (store_summary);
+
+ return success;
+}
+
+gboolean
+camel_m365_store_summary_save (CamelM365StoreSummary *store_summary,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+
+ LOCK (store_summary);
+
+ if (store_summary->priv->dirty) {
+ success = g_key_file_save_to_file (store_summary->priv->key_file,
store_summary->priv->filename, error);
+
+ if (success)
+ store_summary->priv->dirty = FALSE;
+ }
+
+ UNLOCK (store_summary);
+
+ return success;
+}
+
+void
+camel_m365_store_summary_clear (CamelM365StoreSummary *store_summary)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ LOCK (store_summary);
+
+ store_summary->priv->dirty = g_hash_table_size (store_summary->priv->id_full_name_hash) > 0;
+
+ g_key_file_free (store_summary->priv->key_file);
+ store_summary->priv->key_file = g_key_file_new ();
+
+ g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+ g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
+
+ UNLOCK (store_summary);
+}
+
+void
+camel_m365_store_summary_lock (CamelM365StoreSummary *store_summary)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ LOCK (store_summary);
+}
+
+void
+camel_m365_store_summary_unlock (CamelM365StoreSummary *store_summary)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ UNLOCK (store_summary);
+}
+
+static void
+m365_store_summary_build_full_name (const gchar *id,
+ GHashTable *id_folder_name,
+ GHashTable *id_parent_id,
+ GHashTable *covered,
+ GString *inout_full_name)
+{
+ const gchar *parent_id;
+
+ g_return_if_fail (id != NULL);
+
+ if (g_hash_table_contains (covered, id))
+ return;
+
+ g_hash_table_insert (covered, (gpointer) id, NULL);
+
+ parent_id = g_hash_table_lookup (id_parent_id, id);
+
+ if (parent_id && *parent_id && g_hash_table_contains (id_folder_name, parent_id))
+ m365_store_summary_build_full_name (parent_id, id_folder_name, id_parent_id, covered,
inout_full_name);
+
+ if (inout_full_name->len)
+ g_string_append_c (inout_full_name, '/');
+
+ g_string_append (inout_full_name, g_hash_table_lookup (id_folder_name, id));
+}
+
+void
+camel_m365_store_summary_rebuild_hashes (CamelM365StoreSummary *store_summary)
+{
+ GHashTable *id_folder_name;
+ GHashTable *id_parent_id;
+ gchar **groups;
+ gint ii;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ LOCK (store_summary);
+
+ g_hash_table_remove_all (store_summary->priv->full_name_id_hash);
+ g_hash_table_remove_all (store_summary->priv->id_full_name_hash);
+
+ id_folder_name = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+ id_parent_id = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+
+ groups = g_key_file_get_groups (store_summary->priv->key_file, NULL);
+
+ for (ii = 0; groups[ii]; ii++) {
+ const gchar *group = groups[ii];
+
+ if (g_ascii_strcasecmp (group, STORE_GROUP_NAME) != 0 &&
+ g_key_file_has_key (store_summary->priv->key_file, group, "DisplayName", NULL)) {
+ gchar *display_name, *folder_name;
+
+ display_name = g_key_file_get_string (store_summary->priv->key_file, group,
"DisplayName", NULL);
+ folder_name = m365_store_summary_encode_folder_name (display_name);
+
+ g_hash_table_insert (id_folder_name, (gpointer) group, folder_name);
+ g_hash_table_insert (id_parent_id, (gpointer) group,
+ camel_m365_store_summary_dup_folder_parent_id (store_summary, group));
+
+ g_free (display_name);
+ }
+ }
+
+ if (g_hash_table_size (id_folder_name)) {
+ GHashTable *covered;
+ GHashTableIter iter;
+ gpointer key;
+
+ covered = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_hash_table_iter_init (&iter, id_folder_name);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ const gchar *id = key;
+ GString *full_name_str;
+
+ g_hash_table_remove_all (covered);
+
+ full_name_str = g_string_sized_new (16);
+
+ m365_store_summary_build_full_name (id, id_folder_name, id_parent_id, covered,
full_name_str);
+
+ if (full_name_str->len) {
+ gchar *id_dup = g_strdup (id);
+ gchar *full_name = g_string_free (full_name_str, FALSE);
+
+ g_hash_table_insert (store_summary->priv->id_full_name_hash, id_dup,
full_name);
+ g_hash_table_insert (store_summary->priv->full_name_id_hash, full_name,
id_dup);
+ } else {
+ g_string_free (full_name_str, TRUE);
+ }
+ }
+
+ g_hash_table_destroy (covered);
+ }
+
+ g_hash_table_destroy (id_folder_name);
+ g_hash_table_destroy (id_parent_id);
+ g_strfreev (groups);
+
+ UNLOCK (store_summary);
+}
+
+void
+camel_m365_store_summary_set_delta_link (CamelM365StoreSummary *store_summary,
+ const gchar *delta_link)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ LOCK (store_summary);
+
+ g_key_file_set_string (store_summary->priv->key_file, STORE_GROUP_NAME, "DeltaLink", delta_link ?
delta_link : "");
+
+ store_summary->priv->dirty = TRUE;
+
+ UNLOCK (store_summary);
+}
+
+gchar *
+camel_m365_store_summary_dup_delta_link (CamelM365StoreSummary *store_summary)
+{
+ gchar *value;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+
+ LOCK (store_summary);
+
+ value = g_key_file_get_string (store_summary->priv->key_file, STORE_GROUP_NAME, "DeltaLink", NULL);
+
+ UNLOCK (store_summary);
+
+ if (value && !*value) {
+ g_clear_pointer (&value, g_free);
+ }
+
+ return value;
+}
+
+gboolean
+camel_m365_store_summary_has_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gboolean has;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ LOCK (store_summary);
+
+ has = g_hash_table_contains (store_summary->priv->id_full_name_hash, id);
+
+ UNLOCK (store_summary);
+
+ return has;
+}
+
+gboolean
+camel_m365_store_summary_has_full_name (CamelM365StoreSummary *store_summary,
+ const gchar *full_name)
+{
+ gboolean has;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+ g_return_val_if_fail (full_name != NULL, FALSE);
+
+ LOCK (store_summary);
+
+ has = g_hash_table_contains (store_summary->priv->full_name_id_hash, full_name);
+
+ UNLOCK (store_summary);
+
+ return has;
+}
+
+void
+camel_m365_store_summary_remove_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ const gchar *full_name;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+
+ LOCK (store_summary);
+
+ full_name = g_hash_table_lookup (store_summary->priv->id_full_name_hash, id);
+
+ if (full_name) {
+ g_hash_table_remove (store_summary->priv->full_name_id_hash, full_name);
+ g_hash_table_remove (store_summary->priv->id_full_name_hash, id);
+
+ store_summary->priv->dirty = store_summary->priv->dirty ||
+ g_key_file_has_group (store_summary->priv->key_file, id);
+
+ g_key_file_remove_group (store_summary->priv->key_file, id, NULL);
+ }
+
+ UNLOCK (store_summary);
+}
+
+void
+camel_m365_store_summary_set_folder (CamelM365StoreSummary *store_summary,
+ gboolean with_hashes_update,
+ const gchar *id,
+ const gchar *parent_id,
+ const gchar *display_name,
+ gint32 total_count,
+ gint32 unread_count,
+ guint32 flags,
+ EM365FolderKind kind,
+ gboolean is_foreign,
+ gboolean is_public)
+{
+ gboolean changed = FALSE;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (display_name != NULL);
+
+ LOCK (store_summary);
+
+ camel_m365_store_summary_update_folder (store_summary, with_hashes_update, id, parent_id,
display_name, total_count, unread_count, -1);
+
+ camel_m365_store_summary_set_folder_parent_id (store_summary, id, parent_id);
+ camel_m365_store_summary_set_folder_total_count (store_summary, id, total_count);
+ camel_m365_store_summary_set_folder_unread_count (store_summary, id, unread_count);
+ camel_m365_store_summary_set_folder_flags (store_summary, id, flags);
+
+ if (g_key_file_get_integer (store_summary->priv->key_file, id, "Kind", NULL) != kind) {
+ g_key_file_set_integer (store_summary->priv->key_file, id, "Kind", kind);
+ changed = TRUE;
+ }
+
+ if (g_key_file_get_boolean (store_summary->priv->key_file, id, "IsForeign", NULL) != is_foreign) {
+ g_key_file_set_boolean (store_summary->priv->key_file, id, "IsForeign", is_foreign);
+ changed = TRUE;
+ }
+
+ if (g_key_file_get_boolean (store_summary->priv->key_file, id, "IsPublic", NULL) != is_public) {
+ g_key_file_set_boolean (store_summary->priv->key_file, id, "IsPublic", is_public);
+ changed = TRUE;
+ }
+
+ /* Set display name as the last, because it updates internal hashes and depends on the stored data */
+ camel_m365_store_summary_set_folder_display_name (store_summary, id, display_name,
with_hashes_update);
+
+ if (changed)
+ store_summary->priv->dirty = TRUE;
+
+ UNLOCK (store_summary);
+}
+
+void
+camel_m365_store_summary_update_folder (CamelM365StoreSummary *store_summary,
+ gboolean with_hashes_update,
+ const gchar *id,
+ const gchar *parent_id,
+ const gchar *display_name,
+ gint32 total_count,
+ gint32 unread_count,
+ gint32 children_count)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (display_name != NULL);
+
+ LOCK (store_summary);
+
+ camel_m365_store_summary_set_folder_parent_id (store_summary, id, parent_id);
+ camel_m365_store_summary_set_folder_total_count (store_summary, id, total_count);
+ camel_m365_store_summary_set_folder_unread_count (store_summary, id, unread_count);
+
+ if (children_count != -1) {
+ guint32 flags = camel_m365_store_summary_get_folder_flags (store_summary, id);
+
+ flags = (flags & (~(CAMEL_FOLDER_CHILDREN | CAMEL_FOLDER_NOCHILDREN))) |
+ (children_count ? CAMEL_FOLDER_CHILDREN : CAMEL_FOLDER_NOCHILDREN);
+
+ camel_m365_store_summary_set_folder_flags (store_summary, id, flags);
+ }
+
+ /* Set display name as the last, because it updates internal hashes and depends on the stored data */
+ camel_m365_store_summary_set_folder_display_name (store_summary, id, display_name,
with_hashes_update);
+
+ UNLOCK (store_summary);
+}
+
+gboolean
+camel_m365_store_summary_get_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gchar **out_full_name,
+ gchar **out_display_name,
+ gchar **out_parent_id,
+ gint32 *out_total_count,
+ gint32 *out_unread_count,
+ guint32 *out_flags,
+ EM365FolderKind *out_kind,
+ gboolean *out_is_foreign,
+ gboolean *out_is_public)
+{
+ gboolean found;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ LOCK (store_summary);
+
+ found = g_key_file_has_group (store_summary->priv->key_file, id);
+
+ if (found) {
+ if (out_full_name)
+ *out_full_name = g_strdup (g_hash_table_lookup
(store_summary->priv->id_full_name_hash, id));
+
+ if (out_display_name)
+ *out_display_name = g_key_file_get_string (store_summary->priv->key_file, id,
"DisplayName", NULL);
+
+ if (out_parent_id)
+ *out_parent_id = g_key_file_get_string (store_summary->priv->key_file, id,
"ParentId", NULL);
+
+ if (out_total_count)
+ *out_total_count = g_key_file_get_integer (store_summary->priv->key_file, id,
"TotalCount", NULL);
+
+ if (out_unread_count)
+ *out_unread_count = g_key_file_get_integer (store_summary->priv->key_file, id,
"UnreadCount", NULL);
+
+ if (out_flags)
+ *out_flags = g_key_file_get_uint64 (store_summary->priv->key_file, id, "Flags", NULL);
+
+ if (out_kind)
+ *out_kind = g_key_file_get_integer (store_summary->priv->key_file, id, "Kind", NULL);
+
+ if (out_is_foreign)
+ *out_is_foreign = g_key_file_get_boolean (store_summary->priv->key_file, id,
"IsForeign", NULL);
+
+ if (out_is_public)
+ *out_is_public = g_key_file_get_boolean (store_summary->priv->key_file, id,
"IsPublic", NULL);
+ }
+
+ UNLOCK (store_summary);
+
+ return found;
+}
+
+gchar *
+camel_m365_store_summary_dup_folder_full_name (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gchar *value = NULL;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, &value, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL))
+ value = NULL;
+
+ return value;
+}
+
+gchar *
+camel_m365_store_summary_dup_folder_id_for_full_name (CamelM365StoreSummary *store_summary,
+ const gchar *full_name)
+{
+ gchar *id;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+ g_return_val_if_fail (full_name != NULL, NULL);
+
+ LOCK (store_summary);
+
+ id = g_strdup (g_hash_table_lookup (store_summary->priv->full_name_id_hash, full_name));
+
+ UNLOCK (store_summary);
+
+ return id;
+}
+
+gchar *
+camel_m365_store_summary_dup_folder_id_for_type (CamelM365StoreSummary *store_summary,
+ guint32 folder_type)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gchar *id = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+
+ folder_type = folder_type & CAMEL_FOLDER_TYPE_MASK;
+ g_return_val_if_fail (folder_type != 0, NULL);
+
+ LOCK (store_summary);
+
+ g_hash_table_iter_init (&iter, store_summary->priv->id_full_name_hash);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ guint32 flags;
+
+ flags = camel_m365_store_summary_get_folder_flags (store_summary, key);
+
+ if ((flags & CAMEL_FOLDER_TYPE_MASK) == folder_type) {
+ id = g_strdup (key);
+ break;
+ }
+ }
+
+ UNLOCK (store_summary);
+
+ return id;
+}
+
+typedef struct _IdFullNameData {
+ gchar *id;
+ gchar *full_name;
+} IdFullNameData;
+
+static IdFullNameData *
+id_full_name_data_new (gchar *id,
+ gchar *full_name)
+{
+ IdFullNameData *ifnd;
+
+ ifnd = g_slice_new (IdFullNameData);
+ ifnd->id = id;
+ ifnd->full_name = full_name;
+
+ return ifnd;
+}
+
+static void
+id_full_name_data_free (gpointer ptr)
+{
+ IdFullNameData *ifnd = ptr;
+
+ if (ifnd) {
+ g_free (ifnd->id);
+ g_free (ifnd->full_name);
+ g_slice_free (IdFullNameData, ifnd);
+ }
+}
+
+typedef struct _RemovePrefixedData {
+ GHashTable *full_name_id_hash;
+ const gchar *prefix;
+ gint prefix_len;
+ GSList *removed; /* IdFullNameData * */
+} RemovePrefixedData;
+
+static gboolean
+m365_remove_prefixed_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ RemovePrefixedData *rpd = user_data;
+ gchar *id = key, *full_name = value;
+
+ g_return_val_if_fail (rpd != NULL, FALSE);
+ g_return_val_if_fail (full_name != NULL, FALSE);
+
+ if (g_str_has_prefix (full_name, rpd->prefix) &&
+ (!full_name[rpd->prefix_len] || full_name[rpd->prefix_len] == '/')) {
+ g_hash_table_remove (rpd->full_name_id_hash, full_name);
+
+ rpd->removed = g_slist_prepend (rpd->removed, id_full_name_data_new (id, full_name));
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gchar *
+m365_store_summary_build_new_full_name (const gchar *old_full_name,
+ const gchar *new_display_name)
+{
+ gchar *encoded;
+ GString *full_name;
+ const gchar *last_slash;
+
+ g_return_val_if_fail (old_full_name != NULL, NULL);
+ g_return_val_if_fail (new_display_name != NULL, NULL);
+
+ last_slash = strrchr (old_full_name, '/');
+ encoded = m365_store_summary_encode_folder_name (new_display_name);
+ full_name = g_string_sized_new ((last_slash ? (last_slash - old_full_name) : 0) + strlen (encoded) +
2);
+
+ if (last_slash)
+ g_string_append_len (full_name, old_full_name, last_slash - old_full_name + 1);
+
+ g_string_append (full_name, encoded);
+
+ g_free (encoded);
+
+ return g_string_free (full_name, FALSE);
+}
+
+gboolean
+camel_m365_store_summary_set_folder_display_name (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ const gchar *display_name,
+ gboolean with_hashes_update)
+{
+ gchar *current_display_name;
+ gboolean changed = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+ g_return_val_if_fail (display_name != NULL, FALSE);
+
+ LOCK (store_summary);
+
+ current_display_name = g_key_file_get_string (store_summary->priv->key_file, id, "DisplayName", NULL);
+
+ if (g_strcmp0 (current_display_name, display_name) != 0) {
+ const gchar *old_full_name;
+
+ g_key_file_set_string (store_summary->priv->key_file, id, "DisplayName", display_name);
+ store_summary->priv->dirty = TRUE;
+
+ changed = TRUE;
+
+ if (with_hashes_update) {
+ old_full_name = g_hash_table_lookup (store_summary->priv->id_full_name_hash, id);
+
+ if (old_full_name) {
+ RemovePrefixedData rpd;
+ gchar *new_full_name;
+ gint diff;
+ GSList *link;
+
+ rpd.full_name_id_hash = store_summary->priv->full_name_id_hash;
+ rpd.prefix = old_full_name;
+ rpd.prefix_len = strlen (old_full_name);
+ rpd.removed = NULL;
+
+ g_hash_table_foreach_steal (store_summary->priv->id_full_name_hash,
m365_remove_prefixed_cb, &rpd);
+
+ new_full_name = m365_store_summary_build_new_full_name (old_full_name,
display_name);
+ diff = strlen (new_full_name) - rpd.prefix_len;
+
+ for (link = rpd.removed; link; link = g_slist_next (link)) {
+ IdFullNameData *ifnd = link->data;
+ GString *fixed_full_name_str;
+ gchar *fixed_full_name;
+ gint old_full_name_len;
+
+ old_full_name_len = strlen (ifnd->full_name);
+ fixed_full_name_str = g_string_sized_new (old_full_name_len + diff +
2);
+
+ g_string_append (fixed_full_name_str, new_full_name);
+
+ if (old_full_name_len > rpd.prefix_len)
+ g_string_append (fixed_full_name_str, ifnd->full_name +
rpd.prefix_len);
+
+ fixed_full_name = g_string_free (fixed_full_name_str, FALSE);
+
+ g_hash_table_insert (store_summary->priv->id_full_name_hash,
ifnd->id, fixed_full_name);
+ g_hash_table_insert (store_summary->priv->full_name_id_hash,
fixed_full_name, ifnd->id);
+
+ /* To not be freed by id_full_name_data_free() below */
+ ifnd->id = NULL;
+ }
+
+ g_slist_free_full (rpd.removed, id_full_name_data_free);
+ g_free (new_full_name);
+ } else {
+ gchar *parent_id, *encoded_folder_name, *id_copy, *new_full_name = NULL;
+
+ encoded_folder_name = m365_store_summary_encode_folder_name (display_name);
+
+ parent_id = camel_m365_store_summary_dup_folder_parent_id (store_summary, id);
+
+ if (parent_id && *parent_id) {
+ const gchar *parent_full_name;
+
+ parent_full_name = g_hash_table_lookup
(store_summary->priv->id_full_name_hash, parent_id);
+
+ if (parent_full_name && *parent_full_name)
+ new_full_name = g_strconcat (parent_full_name, "/",
encoded_folder_name, NULL);
+ }
+
+ if (!new_full_name) {
+ new_full_name = encoded_folder_name;
+ encoded_folder_name = NULL;
+ }
+
+ g_free (encoded_folder_name);
+ g_free (parent_id);
+
+ id_copy = g_strdup (id);
+
+ g_hash_table_insert (store_summary->priv->id_full_name_hash, id_copy,
new_full_name);
+ g_hash_table_insert (store_summary->priv->full_name_id_hash, new_full_name,
id_copy);
+ }
+ }
+ }
+
+ g_free (current_display_name);
+
+ UNLOCK (store_summary);
+
+ return changed;
+}
+
+gchar *
+camel_m365_store_summary_dup_folder_display_name (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gchar *value = NULL;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, &value, NULL, NULL, NULL, NULL,
NULL, NULL, NULL))
+ value = NULL;
+
+ return value;
+}
+
+void
+camel_m365_store_summary_set_folder_parent_id (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ const gchar *parent_id)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+
+ LOCK (store_summary);
+
+ if (parent_id && *parent_id) {
+ gchar *current_parent_id;
+
+ current_parent_id = g_key_file_get_string (store_summary->priv->key_file, id, "ParentId",
NULL);
+
+ if (g_strcmp0 (current_parent_id, parent_id) != 0) {
+ g_key_file_set_string (store_summary->priv->key_file, id, "ParentId", parent_id);
+ store_summary->priv->dirty = TRUE;
+ }
+
+ g_free (current_parent_id);
+ } else if (g_key_file_has_key (store_summary->priv->key_file, id, "ParentId", NULL)) {
+ g_key_file_remove_key (store_summary->priv->key_file, id, "ParentId", NULL);
+ store_summary->priv->dirty = TRUE;
+ }
+
+ UNLOCK (store_summary);
+}
+
+gchar *
+camel_m365_store_summary_dup_folder_parent_id (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gchar *value = NULL;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, &value, NULL, NULL, NULL,
NULL, NULL, NULL))
+ value = NULL;
+
+ return value;
+}
+
+void
+camel_m365_store_summary_set_folder_total_count (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gint32 total_count)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+
+ LOCK (store_summary);
+
+ if (g_key_file_get_integer (store_summary->priv->key_file, id, "TotalCount", NULL) != total_count) {
+ g_key_file_set_integer (store_summary->priv->key_file, id, "TotalCount", total_count);
+ store_summary->priv->dirty = TRUE;
+ }
+
+ UNLOCK (store_summary);
+}
+
+gint32
+camel_m365_store_summary_get_folder_total_count (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gint32 value = 0;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, &value, NULL, NULL,
NULL, NULL, NULL))
+ value = 0;
+
+ return value;
+}
+
+void
+camel_m365_store_summary_set_folder_unread_count (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gint32 unread_count)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+
+ LOCK (store_summary);
+
+ if (g_key_file_get_integer (store_summary->priv->key_file, id, "UnreadCount", NULL) != unread_count) {
+ g_key_file_set_integer (store_summary->priv->key_file, id, "UnreadCount", unread_count);
+ store_summary->priv->dirty = TRUE;
+ }
+
+ UNLOCK (store_summary);
+}
+
+gint32
+camel_m365_store_summary_get_folder_unread_count (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gint32 value = 0;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, &value, NULL,
NULL, NULL, NULL))
+ value = 0;
+
+ return value;
+}
+
+void
+camel_m365_store_summary_set_folder_flags (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ guint32 flags)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (id != NULL);
+
+ LOCK (store_summary);
+
+ if (g_key_file_get_uint64 (store_summary->priv->key_file, id, "Flags", NULL) != flags) {
+ g_key_file_set_uint64 (store_summary->priv->key_file, id, "Flags", flags);
+ store_summary->priv->dirty = TRUE;
+ }
+
+ UNLOCK (store_summary);
+}
+
+guint32
+camel_m365_store_summary_get_folder_flags (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ guint32 value = 0;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, &value,
NULL, NULL, NULL))
+ value = 0;
+
+ return value;
+}
+
+guint32
+camel_m365_store_summary_get_folder_flags_for_full_name (CamelM365StoreSummary *store_summary,
+ const gchar *full_name)
+{
+ const gchar *id;
+ guint32 flags = 0;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), 0);
+ g_return_val_if_fail (full_name != NULL, 0);
+
+ LOCK (store_summary);
+
+ id = g_hash_table_lookup (store_summary->priv->full_name_id_hash, full_name);
+
+ if (id)
+ flags = camel_m365_store_summary_get_folder_flags (store_summary, id);
+
+ UNLOCK (store_summary);
+
+ return flags;
+}
+
+EM365FolderKind
+camel_m365_store_summary_get_folder_kind (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ EM365FolderKind value = E_M365_FOLDER_KIND_UNKNOWN;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL,
&value, NULL, NULL))
+ value = E_M365_FOLDER_KIND_UNKNOWN;
+
+ return value;
+}
+
+gboolean
+camel_m365_store_summary_get_folder_is_foreign (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gboolean value = FALSE;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, &value, NULL))
+ value = FALSE;
+
+ return value;
+}
+
+gboolean
+camel_m365_store_summary_get_folder_is_public (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ gboolean value = FALSE;
+
+ if (!camel_m365_store_summary_get_folder (store_summary, id, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, &value))
+ value = FALSE;
+
+ return value;
+}
+
+GSList * /* gchar * */
+camel_m365_store_summary_list_folder_ids (CamelM365StoreSummary *store_summary)
+{
+ GSList *ids = NULL;
+ gchar **groups;
+ gint ii;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+
+ LOCK (store_summary);
+
+ groups = g_key_file_get_groups (store_summary->priv->key_file, NULL);
+
+ for (ii = 0; groups[ii]; ii++) {
+ gchar *group = groups[ii];
+
+ if (g_ascii_strcasecmp (group, STORE_GROUP_NAME) != 0 &&
+ g_key_file_has_key (store_summary->priv->key_file, group, "DisplayName", NULL)) {
+ ids = g_slist_prepend (ids, group);
+ } else {
+ g_free (group);
+ }
+ }
+
+ UNLOCK (store_summary);
+
+ /* The array items are moved into the 'ids' GSList or freed */
+ g_free (groups);
+
+ return ids;
+}
+
+CamelFolderInfo *
+camel_m365_store_summary_build_folder_info_for_id (CamelM365StoreSummary *store_summary,
+ const gchar *id)
+{
+ CamelFolderInfo *info;
+ gchar *full_name = NULL;
+ gchar *display_name = NULL;
+ gint32 total_count = 0;
+ gint32 unread_count = 0;
+ guint32 flags = 0;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ LOCK (store_summary);
+
+ if (camel_m365_store_summary_get_folder (store_summary, id, &full_name, &display_name, NULL,
&total_count, &unread_count, &flags, NULL, NULL, NULL)) {
+ info = camel_folder_info_new ();
+ info->full_name = full_name;
+ info->display_name = display_name;
+ info->flags = flags;
+ info->unread = unread_count;
+ info->total = total_count;
+ } else {
+ info = NULL;
+ }
+
+ UNLOCK (store_summary);
+
+ return info;
+}
+
+typedef struct _GatherInfosData {
+ CamelM365StoreSummary *store_summary;
+ GPtrArray *folder_infos;
+ const gchar *prefix;
+ gint prefix_len;
+ gboolean recursive;
+} GatherInfosData;
+
+static void
+m365_store_summary_gather_folder_infos (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ const gchar *id = key, *full_name = value;
+ GatherInfosData *gid = user_data;
+
+ g_return_if_fail (full_name != NULL);
+ g_return_if_fail (gid != NULL);
+
+ if (!gid->prefix_len || (g_str_has_prefix (full_name, gid->prefix) &&
+ (full_name[gid->prefix_len] == '/' || !full_name[gid->prefix_len]))) {
+ const gchar *without_prefix = full_name + gid->prefix_len + (gid->prefix_len > 0 ? 1 : 0);
+
+ if (gid->recursive || !*without_prefix) {
+ CamelFolderInfo *info;
+
+ info = camel_m365_store_summary_build_folder_info_for_id (gid->store_summary, id);
+
+ if (info)
+ g_ptr_array_add (gid->folder_infos, info);
+ else
+ g_warning ("%s: Failed to build folder info for id:'%s' full_name:'%s'",
G_STRFUNC, id, full_name);
+ }
+ }
+}
+
+CamelFolderInfo *
+camel_m365_store_summary_build_folder_info (CamelM365StoreSummary *store_summary,
+ const gchar *top,
+ gboolean recursive)
+{
+ CamelFolderInfo *info = NULL;
+ GatherInfosData gid;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+
+ if (!top)
+ top = "";
+
+ LOCK (store_summary);
+
+ gid.store_summary = store_summary;
+ gid.folder_infos = g_ptr_array_new ();
+ gid.prefix = top;
+ gid.prefix_len = strlen (top);
+ gid.recursive = recursive;
+
+ g_hash_table_foreach (store_summary->priv->id_full_name_hash, m365_store_summary_gather_folder_infos,
&gid);
+
+ info = camel_folder_info_build (gid.folder_infos, top, '/', TRUE);
+
+ UNLOCK (store_summary);
+
+ g_ptr_array_free (gid.folder_infos, TRUE);
+
+ return info;
+}
+
+static void
+m365_store_summary_folder_count_notify_cb (CamelFolderSummary *folder_summary,
+ GParamSpec *param,
+ CamelM365StoreSummary *store_summary)
+{
+ CamelFolder *folder;
+ gchar *folder_id;
+ gint count;
+
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
+ g_return_if_fail (param != NULL);
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+
+ folder = camel_folder_summary_get_folder (folder_summary);
+
+ if (!folder)
+ return;
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_full_name (store_summary,
camel_folder_get_full_name (folder));
+
+ /* This can happen on folder delete/unsubscribe, after folder summary clear */
+ if (!folder_id)
+ return;
+
+ if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0) {
+ count = camel_folder_summary_get_saved_count (folder_summary);
+ camel_m365_store_summary_set_folder_total_count (store_summary, folder_id, count);
+ } else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0) {
+ count = camel_folder_summary_get_unread_count (folder_summary);
+ camel_m365_store_summary_set_folder_unread_count (store_summary, folder_id, count);
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_free (folder_id);
+}
+
+void
+camel_m365_store_summary_connect_folder_summary (CamelM365StoreSummary *store_summary,
+ CamelFolderSummary *folder_summary)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
+
+ g_signal_connect_object (folder_summary, "notify::saved-count", G_CALLBACK
(m365_store_summary_folder_count_notify_cb), store_summary, 0);
+ g_signal_connect_object (folder_summary, "notify::unread-count", G_CALLBACK
(m365_store_summary_folder_count_notify_cb), store_summary, 0);
+}
+
+static gchar *
+camel_m365_category_to_string (const CamelM365Category *cat)
+{
+ gchar *id, *display_name, *color = NULL, *str;
+
+ g_return_val_if_fail (cat != NULL, NULL);
+
+ id = g_uri_escape_string (cat->id, NULL, TRUE);
+ display_name = g_uri_escape_string (cat->display_name, NULL, TRUE);
+
+ if (cat->color)
+ color = g_uri_escape_string (cat->color, NULL, TRUE);
+
+ str = g_strconcat (
+ id ? id : "", "\t",
+ display_name ? display_name : "", "\t",
+ color ? color : "",
+ NULL);
+
+ g_free (id);
+ g_free (display_name);
+ g_free (color);
+
+ return str;
+}
+
+static CamelM365Category *
+camel_m365_category_from_string (const gchar *str)
+{
+ CamelM365Category *cat;
+ gchar **strv, *id, *display_name, *color;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ strv = g_strsplit (str, "\t", -1);
+ if (!strv || !strv[0] || !strv[1]) {
+ g_strfreev (strv);
+ return NULL;
+ }
+
+ id = g_uri_unescape_string (strv[0], NULL);
+ display_name = g_uri_unescape_string (strv[1], NULL);
+ color = (strv[2] && strv[2][0]) ? g_uri_unescape_string (strv[2], NULL) : NULL;
+
+ cat = camel_m365_category_new (id, display_name, color);
+
+ g_free (id);
+ g_free (display_name);
+ g_free (color);
+ g_strfreev (strv);
+
+ return cat;
+}
+
+GHashTable * /* gchar *id ~> CamelM365Category * */
+camel_m365_store_summary_get_categories (CamelM365StoreSummary *store_summary)
+{
+ GHashTable *categories;
+ gchar **strv;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary), NULL);
+
+ LOCK (store_summary);
+
+ strv = g_key_file_get_string_list (store_summary->priv->key_file, STORE_GROUP_NAME, CATEGORIES_KEY,
NULL, NULL);
+
+ UNLOCK (store_summary);
+
+ categories = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, camel_m365_category_free);
+
+ if (strv) {
+ gint ii;
+
+ for (ii = 0; strv[ii]; ii++) {
+ CamelM365Category *cat;
+
+ cat = camel_m365_category_from_string (strv[ii]);
+ if (cat)
+ g_hash_table_insert (categories, cat->id, cat);
+ }
+
+ g_strfreev (strv);
+ }
+
+ return categories;
+}
+
+void
+camel_m365_store_summary_set_categories (CamelM365StoreSummary *store_summary,
+ GHashTable *categories) /* gchar *id ~> CamelM365Category * */
+{
+ GPtrArray *array;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE_SUMMARY (store_summary));
+ g_return_if_fail (categories != NULL);
+
+ array = g_ptr_array_new_full (g_hash_table_size (categories), g_free);
+
+ g_hash_table_iter_init (&iter, categories);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ CamelM365Category *cat = value;
+
+ if (cat) {
+ gchar *str;
+
+ str = camel_m365_category_to_string (cat);
+
+ if (str)
+ g_ptr_array_add (array, str);
+ }
+ }
+
+ LOCK (store_summary);
+
+ g_key_file_set_string_list (store_summary->priv->key_file, STORE_GROUP_NAME, CATEGORIES_KEY,
+ (const gchar * const *) array->pdata, array->len);
+
+ store_summary->priv->dirty = TRUE;
+
+ UNLOCK (store_summary);
+
+ g_ptr_array_free (array, TRUE);
+}
+
+CamelM365Category *
+camel_m365_category_new (const gchar *id,
+ const gchar *display_name,
+ const gchar *color)
+{
+ CamelM365Category *cat;
+
+ g_return_val_if_fail (id != NULL, NULL);
+ g_return_val_if_fail (display_name != NULL, NULL);
+
+ cat = g_slice_new0 (CamelM365Category);
+ cat->id = g_strdup (id);
+ cat->display_name = g_strdup (display_name);
+ cat->color = g_strdup (color);
+
+ return cat;
+}
+
+void
+camel_m365_category_free (gpointer ptr)
+{
+ CamelM365Category *cat = ptr;
+
+ if (cat) {
+ g_free (cat->id);
+ g_free (cat->display_name);
+ g_free (cat->color);
+ g_slice_free (CamelM365Category, cat);
+ }
+}
diff --git a/src/Microsoft365/camel/camel-m365-store-summary.h
b/src/Microsoft365/camel/camel-m365-store-summary.h
new file mode 100644
index 00000000..29f82a4d
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-store-summary.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_STORE_SUMMARY_H
+#define CAMEL_M365_STORE_SUMMARY_H
+
+#include <camel/camel.h>
+
+#include "common/e-m365-enums.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_STORE_SUMMARY \
+ (camel_m365_store_summary_get_type ())
+#define CAMEL_M365_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_STORE_SUMMARY, CamelM365StoreSummary))
+#define CAMEL_M365_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_STORE_SUMMARY, CamelM365StoreSummaryClass))
+#define CAMEL_IS_M365_STORE_SUMMARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_STORE_SUMMARY))
+#define CAMEL_IS_M365_STORE_SUMMARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_STORE_SUMMARY))
+#define CAMEL_M365_STORE_SUMMARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_STORE_SUMMARY, CamelM365StoreSummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelM365StoreSummary CamelM365StoreSummary;
+typedef struct _CamelM365StoreSummaryClass CamelM365StoreSummaryClass;
+typedef struct _CamelM365StoreSummaryPrivate CamelM365StoreSummaryPrivate;
+
+struct _CamelM365StoreSummary {
+ GObject parent;
+ CamelM365StoreSummaryPrivate *priv;
+};
+
+struct _CamelM365StoreSummaryClass {
+ GObjectClass parent_class;
+};
+
+GType camel_m365_store_summary_get_type (void);
+
+CamelM365StoreSummary *
+ camel_m365_store_summary_new (const gchar *filename);
+gboolean camel_m365_store_summary_load (CamelM365StoreSummary *store_summary,
+ GError **error);
+gboolean camel_m365_store_summary_save (CamelM365StoreSummary *store_summary,
+ GError **error);
+void camel_m365_store_summary_clear (CamelM365StoreSummary *store_summary);
+void camel_m365_store_summary_lock (CamelM365StoreSummary *store_summary);
+void camel_m365_store_summary_unlock (CamelM365StoreSummary *store_summary);
+void camel_m365_store_summary_rebuild_hashes (CamelM365StoreSummary *store_summary);
+void camel_m365_store_summary_set_delta_link (CamelM365StoreSummary *store_summary,
+ const gchar *delta_link);
+gchar * camel_m365_store_summary_dup_delta_link (CamelM365StoreSummary *store_summary);
+gboolean camel_m365_store_summary_has_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+gboolean camel_m365_store_summary_has_full_name (CamelM365StoreSummary *store_summary,
+ const gchar *full_name);
+void camel_m365_store_summary_remove_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+void camel_m365_store_summary_set_folder (CamelM365StoreSummary *store_summary,
+ gboolean with_hashes_update,
+ const gchar *id,
+ const gchar *parent_id,
+ const gchar *display_name,
+ gint32 total_count,
+ gint32 unread_count,
+ guint32 flags,
+ EM365FolderKind kind,
+ gboolean is_foreign,
+ gboolean is_public);
+void camel_m365_store_summary_update_folder (CamelM365StoreSummary *store_summary,
+ gboolean with_hashes_update,
+ const gchar *id,
+ const gchar *parent_id,
+ const gchar *display_name,
+ gint32 total_count,
+ gint32 unread_count,
+ gint32 children_count);
+gboolean camel_m365_store_summary_get_folder (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gchar **out_full_name,
+ gchar **out_display_name,
+ gchar **out_parent_id,
+ gint32 *out_total_count,
+ gint32 *out_unread_count,
+ guint32 *out_flags,
+ EM365FolderKind *out_kind,
+ gboolean *out_is_foreign,
+ gboolean *out_is_public);
+gchar * camel_m365_store_summary_dup_folder_full_name
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+gchar * camel_m365_store_summary_dup_folder_id_for_full_name
+ (CamelM365StoreSummary *store_summary,
+ const gchar *full_name);
+gchar * camel_m365_store_summary_dup_folder_id_for_type
+ (CamelM365StoreSummary *store_summary,
+ guint32 folder_type); /* The CAMEL_FOLDER_TYPE_...
of the CamelFolderInfoFlags */
+gboolean camel_m365_store_summary_set_folder_display_name
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ const gchar *display_name,
+ gboolean with_hashes_update);
+gchar * camel_m365_store_summary_dup_folder_display_name
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+void camel_m365_store_summary_set_folder_parent_id
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ const gchar *parent_id);
+gchar * camel_m365_store_summary_dup_folder_parent_id
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+void camel_m365_store_summary_set_folder_total_count
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gint32 total_count);
+gint32 camel_m365_store_summary_get_folder_total_count
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+void camel_m365_store_summary_set_folder_unread_count
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ gint32 unread_count);
+gint32 camel_m365_store_summary_get_folder_unread_count
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+void camel_m365_store_summary_set_folder_flags
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id,
+ guint32 flags);
+guint32 camel_m365_store_summary_get_folder_flags
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+guint32 camel_m365_store_summary_get_folder_flags_for_full_name
+ (CamelM365StoreSummary *store_summary,
+ const gchar *full_name);
+EM365FolderKind camel_m365_store_summary_get_folder_kind(CamelM365StoreSummary *store_summary,
+ const gchar *id);
+gboolean camel_m365_store_summary_get_folder_is_foreign
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+gboolean camel_m365_store_summary_get_folder_is_public
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+GSList * camel_m365_store_summary_list_folder_ids(CamelM365StoreSummary *store_summary); /* gchar
*folder_id */
+CamelFolderInfo *
+ camel_m365_store_summary_build_folder_info_for_id
+ (CamelM365StoreSummary *store_summary,
+ const gchar *id);
+CamelFolderInfo *
+ camel_m365_store_summary_build_folder_info
+ (CamelM365StoreSummary *store_summary,
+ const gchar *top,
+ gboolean recursive);
+void camel_m365_store_summary_connect_folder_summary
+ (CamelM365StoreSummary *store_summary,
+ CamelFolderSummary *folder_summary);
+
+typedef struct _CamelM365Category {
+ gchar *id;
+ gchar *display_name;
+ gchar *color;
+} CamelM365Category;
+
+GHashTable * camel_m365_store_summary_get_categories /* gchar *id ~> CamelM365Category * */
+ (CamelM365StoreSummary *store_summary);
+void camel_m365_store_summary_set_categories
+ (CamelM365StoreSummary *store_summary,
+ GHashTable *categories); /* gchar *id ~> CamelM365Category *
*/
+
+CamelM365Category *
+ camel_m365_category_new (const gchar *id,
+ const gchar *display_name,
+ const gchar *color);
+void camel_m365_category_free (gpointer ptr); /* CamelM365Category * */
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_STORE_SUMMARY_H */
diff --git a/src/Microsoft365/camel/camel-m365-store.c b/src/Microsoft365/camel/camel-m365-store.c
new file mode 100644
index 00000000..4bb2e1ec
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-store.c
@@ -0,0 +1,1831 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include <e-util/e-util.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+#include "camel-m365-folder.h"
+#include "camel-m365-store-summary.h"
+#include "camel-m365-utils.h"
+
+#include "camel-m365-store.h"
+
+#define LOCK(_store) g_rec_mutex_lock (&(_store->priv->property_lock))
+#define UNLOCK(_store) g_rec_mutex_unlock (&(_store->priv->property_lock))
+
+struct _CamelM365StorePrivate {
+ GRecMutex property_lock;
+ gchar *storage_path;
+ CamelM365StoreSummary *summary;
+ EM365Connection *cnc;
+ GHashTable *default_folders;
+};
+
+static void camel_m365_store_initable_init (GInitableIface *iface);
+static void camel_m365_subscribable_init (CamelSubscribableInterface *iface);
+static GInitableIface *parent_initable_interface;
+
+enum {
+ PROP_0,
+ PROP_CONNECTABLE,
+ PROP_HOST_REACHABLE
+};
+
+G_DEFINE_TYPE_WITH_CODE (CamelM365Store, camel_m365_store, CAMEL_TYPE_OFFLINE_STORE,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_m365_store_initable_init)
+ G_IMPLEMENT_INTERFACE (CAMEL_TYPE_NETWORK_SERVICE, NULL)
+ G_IMPLEMENT_INTERFACE (CAMEL_TYPE_SUBSCRIBABLE, camel_m365_subscribable_init)
+ G_ADD_PRIVATE (CamelM365Store))
+
+static gboolean
+m365_store_construct (CamelService *service,
+ CamelSession *session,
+ CamelProvider *provider,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ gchar *summary_file, *session_storage_path;
+ guint32 store_flags;
+ GError *local_error = NULL;
+
+ m365_store = (CamelM365Store *) service;
+
+ store_flags = camel_store_get_flags (CAMEL_STORE (m365_store));
+
+ /* Disable virtual trash and junk folders. Microsoft365 has real folders for that */
+ store_flags &= ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK);
+ store_flags |= CAMEL_STORE_REAL_JUNK_FOLDER;
+ camel_store_set_flags (CAMEL_STORE (m365_store), store_flags);
+
+ session_storage_path = g_strdup (camel_service_get_user_cache_dir (service));
+
+ if (!session_storage_path) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_INVALID,
+ _("Session has no storage path"));
+ return FALSE;
+ }
+
+ m365_store->priv->storage_path = session_storage_path;
+
+ g_mkdir_with_parents (m365_store->priv->storage_path, 0700);
+
+ summary_file = g_build_filename (m365_store->priv->storage_path, "folder-tree", NULL);
+ m365_store->priv->summary = camel_m365_store_summary_new (summary_file);
+
+ if (!camel_m365_store_summary_load (m365_store->priv->summary, &local_error))
+ g_warning ("%s: Failed to load store summary '%s': %s", G_STRFUNC, summary_file, local_error
? local_error->message : "Unknown error");
+
+ g_clear_error (&local_error);
+ g_free (summary_file);
+
+ return TRUE;
+}
+
+static gboolean
+m365_store_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelService *service;
+ CamelSession *session;
+ CamelStore *store;
+ gboolean ret;
+
+ store = CAMEL_STORE (initable);
+ service = CAMEL_SERVICE (initable);
+
+ camel_store_set_flags (store, camel_store_get_flags (store) |
+ CAMEL_STORE_USE_CACHE_DIR |
+ CAMEL_STORE_SUPPORTS_INITIAL_SETUP |
+ CAMEL_STORE_CAN_DELETE_FOLDERS_AT_ONCE);
+
+ /* Chain up to parent interface's method. */
+ if (!parent_initable_interface->init (initable, cancellable, error))
+ return FALSE;
+
+ session = camel_service_ref_session (service);
+
+ ret = m365_store_construct (service, session, NULL, error);
+
+ g_object_unref (session);
+
+ return ret;
+}
+
+static GList *
+m365_store_query_auth_types_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (service), NULL);
+
+ return NULL;
+}
+
+static gchar *
+m365_store_get_name (CamelService *service,
+ gboolean brief)
+{
+ gchar *name;
+
+ if (brief)
+ name = g_strdup (_("Microsoft 365 server"));
+ else
+ name = g_strdup (_("Mail receive via Microsoft 365"));
+
+ return name;
+}
+
+static gboolean
+m365_store_read_default_folders (CamelM365Store *m365_store,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ struct _default_folders {
+ const gchar *name;
+ guint32 flags;
+ } default_folders[] = {
+ { "archive", CAMEL_FOLDER_TYPE_ARCHIVE },
+ { "deleteditems", CAMEL_FOLDER_TYPE_TRASH },
+ { "drafts", CAMEL_FOLDER_TYPE_DRAFTS },
+ { "inbox", CAMEL_FOLDER_TYPE_INBOX },
+ { "junkemail", CAMEL_FOLDER_TYPE_JUNK },
+ { "outbox", CAMEL_FOLDER_TYPE_OUTBOX },
+ { "sentitems", CAMEL_FOLDER_TYPE_SENT }
+ };
+ GPtrArray *requests;
+ gboolean success;
+ guint ii;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), FALSE);
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+
+ LOCK (m365_store);
+
+ if (g_hash_table_size (m365_store->priv->default_folders)) {
+ UNLOCK (m365_store);
+ return TRUE;
+ }
+
+ UNLOCK (m365_store);
+
+ requests = g_ptr_array_new_full (G_N_ELEMENTS (default_folders), g_object_unref);
+
+ for (ii = 0; ii < G_N_ELEMENTS (default_folders); ii++) {
+ SoupMessage *message;
+ gchar *uri;
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, NULL, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ NULL,
+ default_folders[ii].name,
+ "$select", "id",
+ NULL);
+
+ message = soup_message_new (SOUP_METHOD_GET, uri);
+
+ if (!message) {
+ g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Malformed URI: ā%sā"),
uri);
+
+ g_ptr_array_unref (requests);
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ g_ptr_array_add (requests, message);
+ }
+
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0, requests, cancellable, error);
+
+ if (success) {
+ g_warn_if_fail (requests->len == G_N_ELEMENTS (default_folders));
+
+ LOCK (m365_store);
+
+ for (ii = 0; ii < requests->len; ii++) {
+ SoupMessage *message = g_ptr_array_index (requests, ii);
+ JsonNode *node = NULL;
+
+ if (message->status_code > 0 && SOUP_STATUS_IS_SUCCESSFUL (message->status_code) &&
+ e_m365_connection_json_node_from_message (message, NULL, &node, cancellable,
NULL) &&
+ node && JSON_NODE_HOLDS_OBJECT (node)) {
+ JsonObject *object = json_node_get_object (node);
+
+ if (object) {
+ const gchar *id;
+
+ id = e_m365_json_get_string_member (object, "id", NULL);
+
+ if (id && *id) {
+ g_hash_table_insert (m365_store->priv->default_folders,
g_strdup (id),
+ GUINT_TO_POINTER (default_folders[ii].flags));
+ }
+ }
+ }
+
+ if (node)
+ json_node_unref (node);
+ }
+
+ UNLOCK (m365_store);
+ }
+
+ g_ptr_array_unref (requests);
+
+ return success;
+}
+
+static gboolean
+m365_store_equal_label_tag_cb (gconstpointer ptr1,
+ gconstpointer ptr2)
+{
+ const gchar *evo_label_def = ptr1;
+ const gchar *tag = ptr2;
+ const gchar *pos;
+
+ if (!evo_label_def || !tag || !*tag)
+ return FALSE;
+
+ pos = g_strrstr (evo_label_def, tag);
+
+ return pos > evo_label_def && pos[-1] == '|' && !pos[strlen (tag)];
+}
+
+static gboolean
+m365_store_find_in_ptr_array (GPtrArray *haystack,
+ gconstpointer needle,
+ GEqualFunc equal_func,
+ guint *out_index)
+{
+ guint ii;
+
+ if (!haystack)
+ return FALSE;
+
+ if (!equal_func)
+ equal_func = g_direct_equal;
+
+ for (ii = 0; ii < haystack->len; ii++) {
+ if (equal_func (haystack->pdata[ii], needle)) {
+ if (out_index)
+ *out_index = ii;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Returns whether had been done any changes */
+static gboolean
+m365_store_save_category_changes (GHashTable *old_categories, /* gchar *id ~> CamelM365Category * */
+ GHashTable *new_categories) /* gchar *id ~> CamelM365Category * */
+{
+ GHashTableIter iter;
+ GSettings *settings;
+ GPtrArray *evo_labels; /* gchar * (encoded label definition) */
+ gchar **strv;
+ gint ii;
+ gpointer value;
+ gboolean changed = FALSE;
+
+ if (!old_categories || !new_categories)
+ return new_categories != NULL;
+
+ evo_labels = g_ptr_array_new_full (5, g_free);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ strv = g_settings_get_strv (settings, "labels");
+
+ for (ii = 0; strv && strv[ii]; ii++) {
+ g_ptr_array_add (evo_labels, g_strdup (strv[ii]));
+ }
+
+ g_strfreev (strv);
+
+ g_hash_table_iter_init (&iter, new_categories);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ CamelM365Category *new_cat = value, *old_cat;
+ gchar *tag = NULL;
+
+ if (!new_cat)
+ continue;
+
+ old_cat = g_hash_table_lookup (old_categories, new_cat->id);
+ if (old_cat) {
+ if (g_strcmp0 (old_cat->display_name, new_cat->display_name) != 0 ||
+ g_strcmp0 (old_cat->color, new_cat->color) != 0) {
+ /* Old category changed name or color */
+ tag = camel_m365_utils_encode_category_name (new_cat->display_name);
+ }
+ } else {
+ /* This is a new category */
+ tag = camel_m365_utils_encode_category_name (new_cat->display_name);
+ }
+
+ if (tag && *tag) {
+ guint index = (guint) -1;
+ gchar *label_def;
+
+ changed = TRUE;
+
+ /* Sanitize value */
+ for (ii = 0; tag[ii]; ii++) {
+ if (tag[ii] == '|')
+ tag[ii] = '-';
+ }
+
+ if (old_cat && g_strcmp0 (old_cat->display_name, new_cat->display_name) != 0) {
+ gchar *old_tag = camel_m365_utils_encode_category_name
(old_cat->display_name);
+
+ if (old_tag && *old_tag) {
+ if (!m365_store_find_in_ptr_array (evo_labels, old_tag,
m365_store_equal_label_tag_cb, &index))
+ index = (guint) -1;
+ }
+
+ g_free (old_tag);
+ }
+
+ for (ii = 0; new_cat->display_name[ii]; ii++) {
+ if (new_cat->display_name[ii] == '|')
+ new_cat->display_name[ii] = '-';
+ }
+
+ if (index == (guint) -1 &&
+ !m365_store_find_in_ptr_array (evo_labels, tag, m365_store_equal_label_tag_cb,
&index))
+ index = (guint) -1;
+
+ label_def = g_strconcat (new_cat->display_name, "|", new_cat->color ? new_cat->color
: "#FF0000", "|", tag, NULL);
+
+ if (index == (guint) -1 || index >= (gint) evo_labels->len) {
+ g_ptr_array_add (evo_labels, label_def);
+ } else {
+ g_free (evo_labels->pdata[index]);
+ evo_labels->pdata[index] = label_def;
+ }
+ }
+
+ g_hash_table_remove (old_categories, new_cat->id);
+
+ g_free (tag);
+ }
+
+ if (g_hash_table_size (old_categories) > 0) {
+ /* Some categories had been removed */
+ changed = TRUE;
+
+ g_hash_table_iter_init (&iter, old_categories);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ CamelM365Category *old_cat = value;
+ gchar *old_tag;
+ guint index;
+
+ if (!old_cat)
+ continue;
+
+ old_tag = camel_m365_utils_encode_category_name (old_cat->display_name);
+
+ for (ii = 0; old_tag && old_tag[ii]; ii++) {
+ if (old_tag[ii] == '|')
+ old_tag[ii] = '-';
+ }
+
+ if (old_tag &&
+ m365_store_find_in_ptr_array (evo_labels, old_tag, m365_store_equal_label_tag_cb,
&index))
+ g_ptr_array_remove_index (evo_labels, index);
+
+ g_free (old_tag);
+ }
+ }
+
+ if (changed) {
+ /* NULL-terminated array of strings */
+ g_ptr_array_add (evo_labels, NULL);
+
+ g_settings_set_strv (settings, "labels", (const gchar * const *) evo_labels->pdata);
+ }
+
+ g_ptr_array_free (evo_labels, TRUE);
+ g_object_unref (settings);
+
+ return changed;
+}
+
+static void
+m365_store_get_categories_cb (CamelSession *session,
+ GCancellable *cancellable,
+ gpointer user_data,
+ GError **error)
+{
+ CamelM365Store *m365_store = user_data;
+ EM365Connection *cnc;
+ GSList *categories = NULL;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE (m365_store));
+
+ cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (!cnc)
+ return;
+
+ if (e_m365_connection_get_categories_sync (cnc, NULL, &categories, cancellable, error)) {
+ GHashTable *old_categories, *new_categories;
+ GSList *link;
+
+ new_categories = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
camel_m365_category_free);
+
+ for (link = categories; link; link = g_slist_next (link)) {
+ EM365Category *category = link->data;
+ CamelM365Category *cat;
+ const gchar *id, *display_name, *color;
+
+ if (!category)
+ continue;
+
+ id = e_m365_category_get_id (category);
+ display_name = e_m365_category_get_display_name (category);
+ color = e_m365_category_get_color (category);
+
+ if (!id || !display_name)
+ continue;
+
+ if (display_name != camel_m365_utils_rename_label (display_name, TRUE))
+ continue;
+
+ cat = camel_m365_category_new (id, display_name, color);
+
+ if (cat)
+ g_hash_table_insert (new_categories, cat->id, cat);
+ }
+
+ g_slist_free_full (categories, (GDestroyNotify) json_object_unref);
+
+ old_categories = camel_m365_store_summary_get_categories (m365_store->priv->summary);
+
+ if (m365_store_save_category_changes (old_categories, new_categories)) {
+ camel_m365_store_summary_set_categories (m365_store->priv->summary, new_categories);
+ camel_m365_store_summary_save (m365_store->priv->summary, NULL);
+ }
+
+ g_hash_table_destroy (new_categories);
+ g_hash_table_destroy (old_categories);
+ }
+
+ g_object_unref (cnc);
+}
+
+static gboolean
+m365_store_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ EM365Connection *cnc;
+ gboolean success = FALSE;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_m365_store_parent_class)->connect_sync (service, cancellable, error))
+ return FALSE;
+
+ if (camel_service_get_connection_status (service) == CAMEL_SERVICE_DISCONNECTED)
+ return FALSE;
+
+ m365_store = CAMEL_M365_STORE (service);
+ cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (!cnc) {
+ cnc = camel_m365_utils_new_connection (service, NULL);
+
+ if (cnc) {
+ LOCK (m365_store);
+
+ m365_store->priv->cnc = g_object_ref (cnc);
+
+ UNLOCK (m365_store);
+ }
+ }
+
+ if (cnc) {
+ CamelSession *session;
+
+ session = camel_service_ref_session (service);
+
+ success = camel_session_authenticate_sync (session, service, "Microsoft365", cancellable,
error);
+
+ if (success) {
+ camel_session_submit_job (
+ session, _("Look up Microsoft 365 categories"),
+ m365_store_get_categories_cb,
+ g_object_ref (m365_store),
+ g_object_unref);
+ }
+
+ g_clear_object (&session);
+ g_clear_object (&cnc);
+ } else {
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, _("Failed
to create connection"));
+ }
+
+ return success;
+}
+
+static gboolean
+m365_store_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store = CAMEL_M365_STORE (service);
+ EM365Connection *cnc;
+ gboolean success = TRUE;
+
+ cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (cnc) {
+ success = e_m365_connection_disconnect_sync (cnc, cancellable, error);
+
+ g_clear_object (&cnc);
+ }
+
+ if (!success)
+ return FALSE;
+
+ /* Chain up to parent's method. */
+ return CAMEL_SERVICE_CLASS (camel_m365_store_parent_class)->disconnect_sync (service, clean,
cancellable, error);
+}
+
+static CamelAuthenticationResult
+m365_store_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAuthenticationResult result;
+ CamelM365Store *m365_store;
+ EM365Connection *cnc;
+
+ m365_store = CAMEL_M365_STORE (service);
+ cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (!cnc)
+ return CAMEL_AUTHENTICATION_ERROR;
+
+ switch (e_m365_connection_authenticate_sync (cnc, NULL, E_M365_FOLDER_KIND_MAIL, NULL, NULL, NULL,
NULL, cancellable, error)) {
+ case E_SOURCE_AUTHENTICATION_ERROR:
+ case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
+ default:
+ result = CAMEL_AUTHENTICATION_ERROR;
+ break;
+ case E_SOURCE_AUTHENTICATION_ACCEPTED:
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+
+ m365_store_read_default_folders (m365_store, cnc, cancellable, NULL);
+ break;
+ case E_SOURCE_AUTHENTICATION_REJECTED:
+ case E_SOURCE_AUTHENTICATION_REQUIRED:
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ break;
+ }
+
+ g_clear_object (&cnc);
+
+ return result;
+}
+
+static CamelFolder *
+m365_store_get_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelFolder *folder = NULL;
+ gchar *fid, *folder_dir, *display_name;
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ fid = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary, folder_name);
+
+ if (!fid) {
+ g_set_error (
+ error, CAMEL_STORE_ERROR,
+ CAMEL_STORE_ERROR_NO_FOLDER,
+ _("No such folder: %s"), folder_name);
+ return NULL;
+ }
+
+ display_name = camel_m365_store_summary_dup_folder_display_name (m365_store->priv->summary, fid);
+ folder_dir = g_build_filename (m365_store->priv->storage_path, "folders", folder_name, NULL);
+
+ folder = camel_m365_folder_new (store, display_name, folder_name, folder_dir, cancellable, error);
+
+ g_free (display_name);
+ g_free (folder_dir);
+ g_free (fid);
+
+ if (folder && (flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0)
+ camel_folder_prepare_content_refresh (folder);
+
+ return folder;
+}
+
+static void
+m365_store_save_summary (CamelM365StoreSummary *summary,
+ const gchar *where)
+{
+ GError *error = NULL;
+
+ if (!camel_m365_store_summary_save (summary, &error))
+ g_warning ("%s: Failed to save store summary: %s", where, error ? error->message : "Unknown
error");
+
+ g_clear_error (&error);
+}
+
+static CamelFolderInfo *
+m365_store_create_folder_sync (CamelStore *store,
+ const gchar *parent_name,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ EM365MailFolder *mail_folder = NULL;
+ gchar *fid = NULL;
+ gchar *full_name;
+ EM365Connection *cnc;
+ CamelFolderInfo *fi = NULL;
+ guint32 flags;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), NULL);
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ if (parent_name && *parent_name)
+ full_name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ else
+ full_name = g_strdup (folder_name);
+
+ fid = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary, full_name);
+
+ if (fid) {
+ g_free (fid);
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot create folder ā%sā, folder already exists"),
+ full_name);
+ g_free (full_name);
+ return NULL;
+ }
+
+ g_free (full_name);
+
+ /* Get Parent folder ID */
+ if (parent_name && parent_name[0]) {
+ fid = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary,
parent_name);
+
+ if (!fid) {
+ g_set_error (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Parent folder ā%sā does not exist"),
+ parent_name);
+ return NULL;
+ }
+ }
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error)) {
+ g_free (fid);
+
+ return NULL;
+ }
+
+ success = e_m365_connection_create_mail_folder_sync (cnc, NULL, fid, folder_name, &mail_folder,
cancellable, &local_error);
+
+ g_object_unref (cnc);
+ g_free (fid);
+
+ if (!success) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+ g_propagate_error (error, local_error);
+
+ return NULL;
+ }
+
+ flags = e_m365_mail_folder_get_child_folder_count (mail_folder) ? CAMEL_STORE_INFO_FOLDER_CHILDREN :
CAMEL_STORE_INFO_FOLDER_NOCHILDREN;
+
+ camel_m365_store_summary_set_folder (m365_store->priv->summary, TRUE,
+ e_m365_folder_get_id (mail_folder),
+ e_m365_folder_get_parent_folder_id (mail_folder),
+ e_m365_folder_get_display_name (mail_folder),
+ e_m365_mail_folder_get_total_item_count (mail_folder),
+ e_m365_mail_folder_get_unread_item_count (mail_folder),
+ flags, E_M365_FOLDER_KIND_MAIL, FALSE, FALSE);
+
+ fi = camel_m365_store_summary_build_folder_info_for_id (m365_store->priv->summary,
e_m365_folder_get_id (mail_folder));
+
+ camel_store_folder_created (store, fi);
+ camel_subscribable_folder_subscribed (CAMEL_SUBSCRIBABLE (m365_store), fi);
+
+ json_object_unref (mail_folder);
+
+ m365_store_save_summary (m365_store->priv->summary, G_STRFUNC);
+
+ return fi;
+}
+
+static void
+m365_store_notify_created_recursive (CamelStore *store,
+ CamelFolderInfo *folder_info)
+{
+ while (folder_info) {
+ camel_store_folder_created (store, folder_info);
+ camel_subscribable_folder_subscribed (CAMEL_SUBSCRIBABLE (store), folder_info);
+
+ if (folder_info->child)
+ m365_store_notify_created_recursive (store, folder_info->child);
+
+ folder_info = folder_info->next;
+ }
+}
+
+static gboolean
+m365_store_move_mail_folder (CamelM365Store *m365_store,
+ EM365Connection *cnc,
+ const gchar *folder_id,
+ const gchar *des_folder_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365MailFolder *moved_mail_folder = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), FALSE);
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (des_folder_id != NULL, FALSE);
+ g_return_val_if_fail (g_strcmp0 (folder_id, des_folder_id) != 0, FALSE);
+
+ success = e_m365_connection_copy_move_mail_folder_sync (cnc, NULL, folder_id, des_folder_id, FALSE,
&moved_mail_folder, cancellable, error);
+
+ if (success && moved_mail_folder) {
+ CamelFolderInfo *fi;
+ gchar *new_full_name;
+
+ fi = camel_m365_store_summary_build_folder_info_for_id (m365_store->priv->summary, folder_id);
+
+ camel_m365_store_summary_set_folder_parent_id (m365_store->priv->summary, folder_id,
e_m365_folder_get_parent_folder_id (moved_mail_folder));
+ camel_m365_store_summary_rebuild_hashes (m365_store->priv->summary);
+
+ camel_subscribable_folder_unsubscribed (CAMEL_SUBSCRIBABLE (m365_store), fi);
+ camel_store_folder_deleted (CAMEL_STORE (m365_store), fi);
+
+ camel_folder_info_free (fi);
+
+ new_full_name = camel_m365_store_summary_dup_folder_full_name (m365_store->priv->summary,
folder_id);
+ g_warn_if_fail (new_full_name != NULL);
+
+ fi = camel_m365_store_summary_build_folder_info (m365_store->priv->summary, new_full_name,
TRUE);
+
+ m365_store_notify_created_recursive (CAMEL_STORE (m365_store), fi);
+
+ json_object_unref (moved_mail_folder);
+ camel_folder_info_free (fi);
+ g_free (new_full_name);
+ }
+
+ return success;
+}
+
+static void
+m365_store_delete_folders_from_summary_recursive (CamelM365Store *m365_store,
+ CamelFolderInfo *fi,
+ gboolean send_signals)
+{
+ CamelStore *store = send_signals ? CAMEL_STORE (m365_store) : NULL;
+ CamelSubscribable *subscribable = send_signals ? CAMEL_SUBSCRIBABLE (m365_store) : NULL;
+
+ while (fi) {
+ gchar *folder_id;
+
+ if (fi->child)
+ m365_store_delete_folders_from_summary_recursive (m365_store, fi->child,
send_signals);
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary,
fi->full_name);
+ if (folder_id) {
+ camel_m365_store_summary_remove_folder (m365_store->priv->summary, folder_id);
+ g_free (folder_id);
+ }
+
+ if (send_signals) {
+ camel_subscribable_folder_unsubscribed (subscribable, fi);
+ camel_store_folder_deleted (store, fi);
+ }
+
+ fi = fi->next;
+ }
+}
+
+static gboolean
+m365_store_delete_folder_sync (CamelStore *store,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelFolderInfo *folder_info;
+ EM365Connection *cnc = NULL;
+ gchar *folder_id;
+ gchar *trash_folder_id;
+ gchar *trash_full_name;
+ gboolean success;
+ gboolean is_under_trash_folder, claim_unsubscribe = TRUE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), FALSE);
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ folder_info = camel_store_get_folder_info_sync (store, folder_name,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ cancellable, &local_error);
+
+ if (!folder_info) {
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary,
folder_name);
+
+ if (!folder_id) {
+ camel_folder_info_free (folder_info);
+
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Folder does not exist"));
+
+ return FALSE;
+ }
+
+ trash_folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store->priv->summary,
CAMEL_FOLDER_TYPE_TRASH);
+ trash_full_name = camel_m365_store_summary_dup_folder_full_name (m365_store->priv->summary,
trash_folder_id);
+
+ if (!trash_full_name) {
+ camel_folder_info_free (folder_info);
+ g_free (trash_folder_id);
+ g_free (folder_id);
+
+ g_set_error_literal (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot find āDeleted Itemsā
folder"));
+
+ return FALSE;
+ }
+
+ is_under_trash_folder = g_str_has_prefix (folder_name, trash_full_name);
+
+ if (is_under_trash_folder) {
+ gint len = strlen (trash_full_name);
+
+ is_under_trash_folder = len > 0 && (trash_full_name[len - 1] == '/' || folder_name[len] ==
'/');
+ }
+
+ g_free (trash_full_name);
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error)) {
+ camel_folder_info_free (folder_info);
+ g_free (trash_folder_id);
+ g_free (folder_id);
+
+ return FALSE;
+ }
+
+ if (camel_m365_store_summary_get_folder_is_foreign (m365_store->priv->summary, folder_id) ||
+ camel_m365_store_summary_get_folder_is_public (m365_store->priv->summary, folder_id)) {
+ /* do not delete foreign or public folders,
+ * only remove them from the local cache */
+ success = TRUE;
+ } else if (is_under_trash_folder) {
+ success = e_m365_connection_delete_mail_folder_sync (cnc, NULL, folder_id, cancellable,
&local_error);
+ } else {
+ success = m365_store_move_mail_folder (m365_store, cnc, folder_id, "deleteditems",
cancellable, &local_error);
+ claim_unsubscribe = FALSE;
+ }
+
+ g_clear_object (&cnc);
+
+ if (!success) {
+ camel_folder_info_free (folder_info);
+ g_free (trash_folder_id);
+ g_free (folder_id);
+
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+ g_propagate_error (error, local_error);
+
+ return FALSE;
+ }
+
+ if (is_under_trash_folder)
+ m365_store_delete_folders_from_summary_recursive (m365_store, folder_info, FALSE);
+
+ if (claim_unsubscribe) {
+ camel_subscribable_folder_unsubscribed (CAMEL_SUBSCRIBABLE (m365_store), folder_info);
+ camel_store_folder_deleted (store, folder_info);
+ }
+
+ camel_folder_info_free (folder_info);
+
+ m365_store_save_summary (m365_store->priv->summary, G_STRFUNC);
+
+ g_free (trash_folder_id);
+ g_free (folder_id);
+
+ return TRUE;
+}
+
+static gboolean
+m365_store_rename_folder_sync (CamelStore *store,
+ const gchar *old_name,
+ const gchar *new_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ EM365Connection *cnc;
+ const gchar *old_slash, *new_slash;
+ gint parent_len;
+ gchar *folder_id;
+ gboolean success = TRUE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), FALSE);
+
+ if (!g_strcmp0 (old_name, new_name))
+ return TRUE;
+
+ m365_store = CAMEL_M365_STORE (store);
+ folder_id = camel_m365_store_summary_dup_folder_id_for_full_name (m365_store->priv->summary,
old_name);
+
+ if (!folder_id) {
+ g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder ā%sā does not exist"), old_name);
+
+ return FALSE;
+ }
+
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error)) {
+ g_free (folder_id);
+ return FALSE;
+ }
+
+ old_slash = g_strrstr (old_name, "/");
+ new_slash = g_strrstr (new_name, "/");
+
+ if (old_slash)
+ old_slash++;
+ else
+ old_slash = old_name;
+
+ if (new_slash)
+ new_slash++;
+ else
+ new_slash = new_name;
+
+ parent_len = old_slash - old_name;
+
+ /* First move the folder, if needed */
+ if (new_slash - new_name != parent_len ||
+ strncmp (old_name, new_name, parent_len)) {
+ gchar *new_parent_id;
+
+ if (new_slash - new_name > 0) {
+ gchar *new_parent;
+
+ new_parent = g_strndup (new_name, new_slash - new_name - 1);
+ new_parent_id = camel_m365_store_summary_dup_folder_id_for_full_name
(m365_store->priv->summary, new_parent);
+
+ if (!new_parent_id) {
+ g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
+ _("Folder ā%sā does not exist"), new_parent);
+
+ g_free (new_parent);
+ g_free (folder_id);
+
+ return FALSE;
+ }
+
+ g_free (new_parent);
+ } else {
+ new_parent_id = NULL;
+ }
+
+ success = m365_store_move_mail_folder (m365_store, cnc, folder_id, new_parent_id ?
new_parent_id : "msgfolderroot", cancellable, &local_error);
+
+ g_free (new_parent_id);
+ }
+
+ /* Then rename the folder, if needed */
+ if (success && g_strcmp0 (old_slash, new_slash) != 0) {
+ EM365MailFolder *mail_folder = NULL;
+
+ success = e_m365_connection_rename_mail_folder_sync (cnc, NULL, folder_id, new_slash,
&mail_folder, cancellable, &local_error);
+
+ if (mail_folder) {
+ camel_m365_store_summary_set_folder_display_name (m365_store->priv->summary,
folder_id,
+ e_m365_folder_get_display_name (mail_folder), TRUE);
+
+ json_object_unref (mail_folder);
+ }
+ }
+
+ if (success) {
+ CamelFolderInfo *fi;
+
+ fi = camel_m365_store_summary_build_folder_info_for_id (m365_store->priv->summary, folder_id);
+
+ if (fi) {
+ camel_store_folder_renamed (store, old_name, fi);
+ camel_folder_info_free (fi);
+ }
+ }
+
+ m365_store_save_summary (m365_store->priv->summary, G_STRFUNC);
+
+ if (!success && local_error) {
+ camel_m365_store_maybe_disconnect (m365_store, local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ g_free (folder_id);
+
+ return success;
+}
+
+typedef struct _FolderRenamedData {
+ gchar *id;
+ gchar *old_name;
+} FolderRenamedData;
+
+static FolderRenamedData *
+folder_renamed_data_new (gchar *id,
+ gchar *old_name)
+{
+ FolderRenamedData *frd;
+
+ frd = g_slice_new (FolderRenamedData);
+ frd->id = id;
+ frd->old_name = old_name;
+
+ return frd;
+}
+
+static void
+folder_renamed_data_free (gpointer ptr)
+{
+ FolderRenamedData *frd = ptr;
+
+ if (frd) {
+ g_free (frd->id);
+ g_free (frd->old_name);
+ g_slice_free (FolderRenamedData, frd);
+ }
+}
+
+typedef struct _FoldersDeltaData {
+ CamelM365Store *m365_store;
+ GSList *added_ids; /* gchar *, folder ids */
+ GSList *renamed_data; /* FolderRenamedData * */
+ GSList *removed_fis; /* CamelFolderInfo * */
+} FoldersDeltaData;
+
+static gboolean
+camel_m365_got_folders_delta_cb (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned objects from the
server */
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ FoldersDeltaData *fdd = user_data;
+ GSList *link;
+
+ g_return_val_if_fail (fdd, FALSE);
+
+ LOCK (fdd->m365_store);
+
+ for (link = (GSList *) results; link; link = g_slist_next (link)) {
+ JsonObject *object = link->data;
+ const gchar *id = e_m365_folder_get_id (object);
+
+ if (e_m365_delta_is_removed_object (object)) {
+ CamelFolderInfo *info;
+
+ info = camel_m365_store_summary_build_folder_info_for_id
(fdd->m365_store->priv->summary, id);
+
+ if (info)
+ fdd->removed_fis = g_slist_prepend (fdd->removed_fis, info);
+
+ camel_m365_store_summary_remove_folder (fdd->m365_store->priv->summary, id);
+ } else {
+ gchar *old_full_name = NULL;
+ guint32 flags;
+
+ if (camel_m365_store_summary_has_folder (fdd->m365_store->priv->summary, id))
+ old_full_name = camel_m365_store_summary_dup_folder_full_name
(fdd->m365_store->priv->summary, id);
+
+ flags = e_m365_mail_folder_get_child_folder_count (object) ?
CAMEL_STORE_INFO_FOLDER_CHILDREN : CAMEL_STORE_INFO_FOLDER_NOCHILDREN;
+
+ flags |= GPOINTER_TO_UINT (g_hash_table_lookup
(fdd->m365_store->priv->default_folders, id));
+
+ camel_m365_store_summary_set_folder (fdd->m365_store->priv->summary, FALSE, id,
+ e_m365_folder_get_parent_folder_id (object),
+ e_m365_folder_get_display_name (object),
+ e_m365_mail_folder_get_total_item_count (object),
+ e_m365_mail_folder_get_unread_item_count (object),
+ flags, E_M365_FOLDER_KIND_MAIL, FALSE, FALSE);
+
+ if (old_full_name)
+ fdd->renamed_data = g_slist_prepend (fdd->renamed_data,
folder_renamed_data_new (g_strdup (id), old_full_name));
+ else
+ fdd->added_ids = g_slist_prepend (fdd->added_ids, g_strdup (id));
+ }
+ }
+
+ UNLOCK (fdd->m365_store);
+
+ return TRUE;
+}
+
+static void
+m365_store_forget_all_folders (CamelM365Store *m365_store)
+{
+ CamelStore *store;
+ CamelSubscribable *subscribable;
+ GSList *ids, *link;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE (m365_store));
+
+ store = CAMEL_STORE (m365_store);
+ subscribable = CAMEL_SUBSCRIBABLE (m365_store);
+ ids = camel_m365_store_summary_list_folder_ids (m365_store->priv->summary);
+
+ if (!ids)
+ return;
+
+ for (link = ids; link; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+ CamelFolderInfo *fi;
+
+ fi = camel_m365_store_summary_build_folder_info_for_id (m365_store->priv->summary, id);
+ camel_subscribable_folder_unsubscribed (subscribable, fi);
+ camel_store_folder_deleted (store, fi);
+ camel_folder_info_free (fi);
+ }
+
+ g_slist_free_full (ids, g_free);
+
+ camel_m365_store_summary_set_delta_link (m365_store->priv->summary, "");
+ camel_m365_store_summary_clear (m365_store->priv->summary);
+}
+
+static CamelFolderInfo *
+m365_store_get_folder_info_sync (CamelStore *store,
+ const gchar *top,
+ guint32 flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelFolderInfo *fi;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), NULL);
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (m365_store))) {
+ gboolean refresh_online;
+
+ refresh_online = !(flags & CAMEL_STORE_FOLDER_INFO_FAST) ||
+ (flags & CAMEL_STORE_FOLDER_INFO_REFRESH) != 0;
+
+ if (!refresh_online) {
+ gchar *delta_link;
+
+ LOCK (m365_store);
+
+ delta_link = camel_m365_store_summary_dup_delta_link (m365_store->priv->summary);
+ refresh_online = !delta_link || !*delta_link;
+ g_free (delta_link);
+
+ UNLOCK (m365_store);
+ }
+
+ if (refresh_online) {
+ EM365Connection *cnc;
+
+ cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (cnc) {
+ FoldersDeltaData fdd;
+ gchar *old_delta_link, *new_delta_link = NULL;
+ GError *local_error = NULL;
+
+ LOCK (m365_store);
+
+ old_delta_link = camel_m365_store_summary_dup_delta_link
(m365_store->priv->summary);
+
+ UNLOCK (m365_store);
+
+ fdd.m365_store = m365_store;
+ fdd.added_ids = NULL;
+ fdd.renamed_data = NULL;
+ fdd.removed_fis = NULL;
+
+ success = e_m365_connection_get_folders_delta_sync (cnc, NULL,
E_M365_FOLDER_KIND_MAIL, NULL, old_delta_link, 0,
+ camel_m365_got_folders_delta_cb, &fdd, &new_delta_link, cancellable,
&local_error);
+
+ if (old_delta_link && *old_delta_link &&
e_m365_connection_util_delta_token_failed (local_error)) {
+ g_clear_pointer (&old_delta_link, g_free);
+ g_clear_error (&local_error);
+
+ m365_store_forget_all_folders (m365_store);
+
+ success = e_m365_connection_get_folders_delta_sync (cnc, NULL,
E_M365_FOLDER_KIND_MAIL, NULL, NULL, 0,
+ camel_m365_got_folders_delta_cb, &fdd, &new_delta_link,
cancellable, error);
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ if (success) {
+ CamelSubscribable *subscribable = CAMEL_SUBSCRIBABLE (m365_store);
+ CamelFolderInfo *info;
+ GSList *link;
+
+ LOCK (m365_store);
+
+ camel_m365_store_summary_set_delta_link (m365_store->priv->summary,
new_delta_link);
+ m365_store_save_summary (m365_store->priv->summary, G_STRFUNC);
+
+ fdd.added_ids = g_slist_reverse (fdd.added_ids);
+ fdd.renamed_data = g_slist_reverse (fdd.renamed_data);
+ fdd.removed_fis = g_slist_reverse (fdd.removed_fis);
+
+ if (fdd.added_ids || fdd.renamed_data || fdd.removed_fis)
+ camel_m365_store_summary_rebuild_hashes
(m365_store->priv->summary);
+
+ for (link = fdd.removed_fis; link; link = g_slist_next (link)) {
+ info = link->data;
+
+ camel_subscribable_folder_unsubscribed (subscribable, info);
+ camel_store_folder_deleted (store, info);
+ }
+
+ for (link = fdd.added_ids; link; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+
+ info = camel_m365_store_summary_build_folder_info_for_id
(m365_store->priv->summary, id);
+
+ if (info) {
+ camel_store_folder_created (store, info);
+ camel_subscribable_folder_subscribed (subscribable,
info);
+ camel_folder_info_free (info);
+ }
+ }
+
+ for (link = fdd.renamed_data; link; link = g_slist_next (link)) {
+ const FolderRenamedData *frd = link->data;
+
+ info = camel_m365_store_summary_build_folder_info_for_id
(m365_store->priv->summary, frd->id);
+
+ if (info) {
+ camel_store_folder_renamed (store, frd->old_name,
info);
+ camel_folder_info_free (info);
+ }
+ }
+
+ UNLOCK (m365_store);
+ }
+
+ g_slist_free_full (fdd.added_ids, g_free);
+ g_slist_free_full (fdd.renamed_data, folder_renamed_data_free);
+ g_slist_free_full (fdd.removed_fis, (GDestroyNotify) camel_folder_info_free);
+
+ g_clear_object (&cnc);
+ g_free (old_delta_link);
+ g_free (new_delta_link);
+ }
+ }
+ }
+
+ if (success) {
+ LOCK (m365_store);
+
+ fi = camel_m365_store_summary_build_folder_info (m365_store->priv->summary, top, (flags &
CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0);
+
+ UNLOCK (m365_store);
+ } else {
+ fi = NULL;
+ }
+
+ return fi;
+}
+
+/* Hold the property lock before calling this function */
+static void
+m365_store_save_setup_folder_locked (CamelM365Store *m365_store,
+ GHashTable *save_setup,
+ guint32 folder_type, /* one of TYPE constants from CamelFolderInfoFlags
*/
+ const gchar *property_name)
+{
+ gchar *folder_id;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE (m365_store));
+ g_return_if_fail (save_setup != NULL);
+ g_return_if_fail (folder_type != 0);
+ g_return_if_fail (property_name != NULL);
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store->priv->summary, folder_type);
+
+ if (folder_id) {
+ gchar *fullname;
+
+ fullname = camel_m365_store_summary_dup_folder_full_name (m365_store->priv->summary,
folder_id);
+
+ if (fullname && *fullname) {
+ g_hash_table_insert (save_setup,
+ g_strdup (property_name),
+ fullname);
+
+ fullname = NULL;
+ }
+
+ g_free (fullname);
+ g_free (folder_id);
+ }
+}
+
+static gboolean
+m365_store_initial_setup_with_connection_sync (CamelStore *store,
+ GHashTable *save_setup,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ if (cnc) {
+ g_object_ref (cnc);
+ } else {
+ if (!camel_m365_store_ensure_connected (m365_store, &cnc, cancellable, error))
+ return FALSE;
+
+ g_return_val_if_fail (cnc != NULL, FALSE);
+ }
+
+ if (!m365_store_read_default_folders (m365_store, cnc, cancellable, error)) {
+ g_clear_object (&cnc);
+ return FALSE;
+ }
+
+ if (save_setup) {
+ LOCK (m365_store);
+
+ m365_store_save_setup_folder_locked (m365_store, save_setup, CAMEL_FOLDER_TYPE_SENT,
CAMEL_STORE_SETUP_SENT_FOLDER);
+ m365_store_save_setup_folder_locked (m365_store, save_setup, CAMEL_FOLDER_TYPE_DRAFTS,
CAMEL_STORE_SETUP_DRAFTS_FOLDER);
+ m365_store_save_setup_folder_locked (m365_store, save_setup, CAMEL_FOLDER_TYPE_ARCHIVE,
CAMEL_STORE_SETUP_ARCHIVE_FOLDER);
+
+ UNLOCK (m365_store);
+ }
+
+ g_clear_object (&cnc);
+
+ return TRUE;
+}
+
+static gboolean
+m365_store_initial_setup_sync (CamelStore *store,
+ GHashTable *save_setup,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return m365_store_initial_setup_with_connection_sync (store, save_setup, NULL, cancellable, error);
+}
+
+static CamelFolder *
+m365_store_get_trash_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelFolder *folder = NULL;
+ gchar *folder_id, *folder_name;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), NULL);
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ LOCK (m365_store);
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store->priv->summary,
CAMEL_FOLDER_TYPE_TRASH);
+
+ if (!folder_id) {
+ UNLOCK (m365_store);
+ g_set_error_literal (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Could not
locate Trash folder"));
+ return NULL;
+ }
+
+ folder_name = camel_m365_store_summary_dup_folder_full_name (m365_store->priv->summary, folder_id);
+
+ UNLOCK (m365_store);
+
+ folder = camel_store_get_folder_sync (store, folder_name, 0, cancellable, error);
+
+ g_free (folder_name);
+ g_free (folder_id);
+
+ if (folder) {
+ GPtrArray *folders;
+ gboolean can = TRUE;
+ guint ii;
+
+ /* Save content of all opened folders, thus any messages deleted in them
+ are moved to the Deleted Items folder first, thus in case of the trash
+ folder instance being used to expunge messages will contain all of them.
+ */
+ folders = camel_store_dup_opened_folders (store);
+
+ for (ii = 0; ii < folders->len; ii++) {
+ CamelFolder *secfolder = folders->pdata[ii];
+
+ if (secfolder != folder && can)
+ can = camel_folder_synchronize_sync (secfolder, FALSE, cancellable, NULL);
+
+ g_object_unref (secfolder);
+ }
+ g_ptr_array_free (folders, TRUE);
+
+ /* To return 'Deleted Items' folder with current content,
+ not with possibly stale locally cached copy. */
+ camel_folder_refresh_info_sync (folder, cancellable, NULL);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+m365_store_get_junk_folder_sync (CamelStore *store,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Store *m365_store;
+ CamelFolder *folder = NULL;
+ gchar *folder_id, *folder_name;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (store), NULL);
+
+ m365_store = CAMEL_M365_STORE (store);
+
+ folder_id = camel_m365_store_summary_dup_folder_id_for_type (m365_store->priv->summary,
CAMEL_FOLDER_TYPE_JUNK);
+
+ if (!folder_id) {
+ g_set_error_literal (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Could not
locate Junk folder"));
+ return NULL;
+ }
+
+ folder_name = camel_m365_store_summary_dup_folder_full_name (m365_store->priv->summary, folder_id);
+
+ folder = camel_store_get_folder_sync (store, folder_name, 0, cancellable, error);
+
+ g_free (folder_name);
+ g_free (folder_id);
+
+ return folder;
+}
+
+static gboolean
+m365_store_can_refresh_folder (CamelStore *store,
+ CamelFolderInfo *info,
+ GError **error)
+{
+ CamelSettings *settings;
+ CamelM365Settings *m365_settings;
+ gboolean check_all;
+
+ /* Skip unselectable folders from automatic refresh */
+ if (info && (info->flags & CAMEL_FOLDER_NOSELECT) != 0)
+ return FALSE;
+
+ settings = camel_service_ref_settings (CAMEL_SERVICE (store));
+
+ m365_settings = CAMEL_M365_SETTINGS (settings);
+ check_all = camel_m365_settings_get_check_all (m365_settings);
+
+ g_object_unref (settings);
+
+ if (check_all)
+ return TRUE;
+
+ /* Delegate decision to parent class */
+ return CAMEL_STORE_CLASS (camel_m365_store_parent_class)->can_refresh_folder (store, info, error);
+}
+
+static gboolean
+m365_store_folder_is_subscribed (CamelSubscribable *subscribable,
+ const gchar *folder_name)
+{
+ CamelM365Store *m365_store = CAMEL_M365_STORE (subscribable);
+
+ return camel_m365_store_summary_has_full_name (m365_store->priv->summary, folder_name);
+}
+
+static gboolean
+m365_store_subscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static gboolean
+m365_store_unsubscribe_folder_sync (CamelSubscribable *subscribable,
+ const gchar *folder_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+m365_store_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ camel_network_service_set_connectable (
+ CAMEL_NETWORK_SERVICE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_store_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONNECTABLE:
+ g_value_take_object (
+ value,
+ camel_network_service_ref_connectable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+ case PROP_HOST_REACHABLE:
+ g_value_set_boolean (
+ value,
+ camel_network_service_get_host_reachable (
+ CAMEL_NETWORK_SERVICE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_store_dispose (GObject *object)
+{
+ CamelM365Store *m365_store = CAMEL_M365_STORE (object);
+
+ LOCK (m365_store);
+
+ if (m365_store->priv->summary) {
+ m365_store_save_summary (m365_store->priv->summary, G_STRFUNC);
+ g_clear_object (&m365_store->priv->summary);
+ }
+
+ g_clear_object (&m365_store->priv->cnc);
+
+ UNLOCK (m365_store);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_store_parent_class)->dispose (object);
+}
+
+static void
+m365_store_finalize (GObject *object)
+{
+ CamelM365Store *m365_store;
+
+ m365_store = CAMEL_M365_STORE (object);
+
+ g_rec_mutex_clear (&m365_store->priv->property_lock);
+ g_hash_table_destroy (m365_store->priv->default_folders);
+ g_free (m365_store->priv->storage_path);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_store_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_store_class_init (CamelM365StoreClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelStoreClass *store_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = m365_store_set_property;
+ object_class->get_property = m365_store_get_property;
+ object_class->dispose = m365_store_dispose;
+ object_class->finalize = m365_store_finalize;
+
+ /* Inherited from CamelNetworkService */
+ g_object_class_override_property (
+ object_class,
+ PROP_CONNECTABLE,
+ "connectable");
+
+ /* Inherited from CamelNetworkService */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST_REACHABLE,
+ "host-reachable");
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_M365_SETTINGS;
+ service_class->query_auth_types_sync = m365_store_query_auth_types_sync;
+ service_class->get_name = m365_store_get_name;
+ service_class->connect_sync = m365_store_connect_sync;
+ service_class->disconnect_sync = m365_store_disconnect_sync;
+ service_class->authenticate_sync = m365_store_authenticate_sync;
+
+ store_class = CAMEL_STORE_CLASS (class);
+ store_class->get_folder_sync = m365_store_get_folder_sync;
+ store_class->create_folder_sync = m365_store_create_folder_sync;
+ store_class->delete_folder_sync = m365_store_delete_folder_sync;
+ store_class->rename_folder_sync = m365_store_rename_folder_sync;
+ store_class->get_folder_info_sync = m365_store_get_folder_info_sync;
+ store_class->initial_setup_sync = m365_store_initial_setup_sync;
+ store_class->get_trash_folder_sync = m365_store_get_trash_folder_sync;
+ store_class->get_junk_folder_sync = m365_store_get_junk_folder_sync;
+ store_class->can_refresh_folder = m365_store_can_refresh_folder;
+}
+
+static void
+camel_m365_store_initable_init (GInitableIface *iface)
+{
+ parent_initable_interface = g_type_interface_peek_parent (iface);
+
+ iface->init = m365_store_initable_init;
+}
+
+static void
+camel_m365_subscribable_init (CamelSubscribableInterface *iface)
+{
+ iface->folder_is_subscribed = m365_store_folder_is_subscribed;
+ iface->subscribe_folder_sync = m365_store_subscribe_folder_sync;
+ iface->unsubscribe_folder_sync = m365_store_unsubscribe_folder_sync;
+}
+
+static void
+camel_m365_store_init (CamelM365Store *m365_store)
+{
+ m365_store->priv = camel_m365_store_get_instance_private (m365_store);
+
+ g_rec_mutex_init (&m365_store->priv->property_lock);
+ m365_store->priv->default_folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+CamelM365StoreSummary *
+camel_m365_store_ref_store_summary (CamelM365Store *m365_store)
+{
+ CamelM365StoreSummary *summary;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), NULL);
+
+ LOCK (m365_store);
+
+ summary = m365_store->priv->summary;
+
+ if (summary)
+ g_object_ref (summary);
+
+ UNLOCK (m365_store);
+
+ return summary;
+}
+
+EM365Connection *
+camel_m365_store_ref_connection (CamelM365Store *m365_store)
+{
+ EM365Connection *cnc = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), NULL);
+
+ LOCK (m365_store);
+
+ if (m365_store->priv->cnc)
+ cnc = g_object_ref (m365_store->priv->cnc);
+
+ UNLOCK (m365_store);
+
+ return cnc;
+}
+
+gboolean
+camel_m365_store_ensure_connected (CamelM365Store *m365_store,
+ EM365Connection **out_cnc, /* out, nullable, transfer full */
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_STORE (m365_store), FALSE);
+
+ if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (m365_store))) {
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+
+ return FALSE;
+ }
+
+ if (!camel_service_connect_sync ((CamelService *) m365_store, cancellable, error))
+ return FALSE;
+
+ if (out_cnc) {
+ *out_cnc = camel_m365_store_ref_connection (m365_store);
+
+ if (!*out_cnc) {
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
+ _("You must be working online to complete this operation"));
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void
+camel_m365_store_maybe_disconnect (CamelM365Store *m365_store,
+ const GError *error)
+{
+ CamelService *service;
+
+ g_return_if_fail (CAMEL_IS_M365_STORE (m365_store));
+
+ if (!error)
+ return;
+
+ service = CAMEL_SERVICE (m365_store);
+
+ if (camel_service_get_connection_status (service) != CAMEL_SERVICE_CONNECTED)
+ return;
+
+#if 0
+ if (g_error_matches (error, M365_CONNECTION_ERROR, M365_CONNECTION_ERROR_NORESPONSE) ||
+ g_error_matches (error, M365_CONNECTION_ERROR, M365_CONNECTION_ERROR_AUTHENTICATION_FAILED))
+ camel_service_disconnect_sync (service, FALSE, NULL, NULL);
+#endif
+}
+
+void
+camel_m365_store_connect_folder_summary (CamelM365Store *m365_store,
+ CamelFolderSummary *folder_summary)
+{
+ g_return_if_fail (CAMEL_IS_M365_STORE (m365_store));
+ g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
+
+ LOCK (m365_store);
+
+ if (m365_store->priv->summary)
+ camel_m365_store_summary_connect_folder_summary (m365_store->priv->summary, folder_summary);
+
+ UNLOCK (m365_store);
+}
diff --git a/src/Microsoft365/camel/camel-m365-store.h b/src/Microsoft365/camel/camel-m365-store.h
new file mode 100644
index 00000000..c20d207c
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-store.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_STORE_H
+#define CAMEL_M365_STORE_H
+
+#include <camel/camel.h>
+
+#include "common/e-m365-connection.h"
+#include "camel-m365-store-summary.h"
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_STORE \
+ (camel_m365_store_get_type ())
+#define CAMEL_M365_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_STORE, CamelM365Store))
+#define CAMEL_M365_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_STORE, CamelM365StoreClass))
+#define CAMEL_IS_M365_STORE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_STORE))
+#define CAMEL_IS_M365_STORE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_STORE))
+#define CAMEL_M365_STORE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_STORE, CamelM365StoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelM365Store CamelM365Store;
+typedef struct _CamelM365StoreClass CamelM365StoreClass;
+typedef struct _CamelM365StorePrivate CamelM365StorePrivate;
+
+struct _CamelM365Store {
+ CamelOfflineStore parent;
+ CamelM365StorePrivate *priv;
+};
+
+struct _CamelM365StoreClass {
+ CamelOfflineStoreClass parent_class;
+};
+
+GType camel_m365_store_get_type (void);
+
+CamelM365StoreSummary *
+ camel_m365_store_ref_store_summary
+ (CamelM365Store *m365_store);
+EM365Connection *
+ camel_m365_store_ref_connection (CamelM365Store *m365_store);
+gboolean camel_m365_store_ensure_connected
+ (CamelM365Store *m365_store,
+ EM365Connection **out_cnc, /* out, nullable, transfer full */
+ GCancellable *cancellable,
+ GError **error);
+void camel_m365_store_maybe_disconnect
+ (CamelM365Store *m365_store,
+ const GError *error);
+void camel_m365_store_connect_folder_summary
+ (CamelM365Store *m365_store,
+ CamelFolderSummary *folder_summary);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_STORE_H */
diff --git a/src/Microsoft365/camel/camel-m365-transport.c b/src/Microsoft365/camel/camel-m365-transport.c
new file mode 100644
index 00000000..fb12f412
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-transport.c
@@ -0,0 +1,400 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <libemail-engine/libemail-engine.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+#include "camel-m365-store.h"
+#include "camel-m365-utils.h"
+
+#include "camel-m365-transport.h"
+
+#define LOCK(_transport) g_mutex_lock (&_transport->priv->property_lock)
+#define UNLOCK(_transport) g_mutex_unlock (&_transport->priv->property_lock)
+
+struct _CamelM365TransportPrivate
+{
+ GMutex property_lock;
+ EM365Connection *cnc;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CamelM365Transport, camel_m365_transport, CAMEL_TYPE_TRANSPORT)
+
+static gboolean
+m365_transport_is_server_side_sent_folder (CamelService *service,
+ GCancellable *cancellable)
+{
+ CamelSession *session;
+ ESourceRegistry *registry;
+ ESource *sibling, *source = NULL;
+ gboolean is_server_side = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_M365_TRANSPORT (service), FALSE);
+
+ session = camel_service_ref_session (service);
+
+ if (session && E_IS_MAIL_SESSION (session))
+ registry = g_object_ref (e_mail_session_get_registry (E_MAIL_SESSION (session)));
+ else
+ registry = e_source_registry_new_sync (cancellable, NULL);
+
+ if (!registry) {
+ g_clear_object (&session);
+ return FALSE;
+ }
+
+ sibling = e_source_registry_ref_source (registry, camel_service_get_uid (service));
+
+ if (sibling) {
+ GList *sources, *siter;
+
+ sources = e_source_registry_list_sources (registry, E_SOURCE_EXTENSION_MAIL_SUBMISSION);
+
+ for (siter = sources; siter; siter = siter->next) {
+ source = siter->data;
+
+ if (!source || g_strcmp0 (e_source_get_parent (source), e_source_get_parent
(sibling)) != 0 ||
+ !e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_SUBMISSION) ||
+ !e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION))
+ source = NULL;
+ else
+ break;
+ }
+
+ if (source &&
+ e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_SUBMISSION) &&
+ e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
+ ESourceMailSubmission *subm_extension;
+ CamelStore *store = NULL;
+ gchar *folder_name = NULL;
+
+ subm_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_SUBMISSION);
+
+ /* Copy messages on the server side only if the replies
+ might not be saved to the original folder, which is handled
+ by the evolution itself. */
+ if (!e_source_mail_submission_get_replies_to_origin_folder (subm_extension) &&
+ e_source_mail_submission_get_sent_folder (subm_extension) &&
+ e_mail_folder_uri_parse (session,
+ e_source_mail_submission_get_sent_folder (subm_extension),
+ &store, &folder_name, NULL) && CAMEL_IS_M365_STORE (store)) {
+ CamelM365Store *m365_store = CAMEL_M365_STORE (store);
+ CamelM365StoreSummary *m365_store_summary;
+ gchar *folder_id_str;
+
+ m365_store_summary = camel_m365_store_ref_store_summary (m365_store);
+ folder_id_str = camel_m365_store_summary_dup_folder_id_for_full_name
(m365_store_summary, folder_name);
+ if (folder_id_str && *folder_id_str) {
+ guint32 flags;
+
+ flags = camel_m365_store_summary_get_folder_flags
(m365_store_summary, folder_id_str);
+
+ if ((flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_SENT) {
+ is_server_side = TRUE;
+ }
+ }
+
+ g_clear_object (&m365_store_summary);
+ g_free (folder_id_str);
+ }
+
+ g_clear_object (&store);
+ g_free (folder_name);
+ }
+
+ g_list_free_full (sources, g_object_unref);
+ g_object_unref (sibling);
+ }
+
+ g_object_unref (registry);
+ g_clear_object (&session);
+
+ return is_server_side;
+}
+
+static EM365Connection *
+m365_transport_ref_connection (CamelM365Transport *m365_transport)
+{
+ EM365Connection *cnc = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_M365_TRANSPORT (m365_transport), NULL);
+
+ LOCK (m365_transport);
+
+ if (m365_transport->priv->cnc)
+ cnc = g_object_ref (m365_transport->priv->cnc);
+
+ UNLOCK (m365_transport);
+
+ return cnc;
+}
+
+static gboolean
+m365_transport_connect_sync (CamelService *service,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Transport *m365_transport;
+ EM365Connection *cnc;
+ gboolean success = FALSE;
+
+ /* Chain up to parent's method. */
+ if (!CAMEL_SERVICE_CLASS (camel_m365_transport_parent_class)->connect_sync (service, cancellable,
error))
+ return FALSE;
+
+ if (camel_service_get_connection_status (service) == CAMEL_SERVICE_DISCONNECTED)
+ return FALSE;
+
+ m365_transport = CAMEL_M365_TRANSPORT (service);
+ cnc = m365_transport_ref_connection (m365_transport);
+
+ if (!cnc) {
+ cnc = camel_m365_utils_new_connection (service, NULL);
+
+ if (cnc) {
+ LOCK (m365_transport);
+
+ m365_transport->priv->cnc = g_object_ref (cnc);
+
+ UNLOCK (m365_transport);
+ }
+ }
+
+ if (cnc) {
+ CamelSession *session;
+
+ session = camel_service_ref_session (service);
+
+ success = camel_session_authenticate_sync (session, service, "Microsoft365", cancellable,
error);
+
+ g_clear_object (&session);
+ g_clear_object (&cnc);
+ } else {
+ g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, _("Failed
to create connection"));
+ }
+
+ return success;
+}
+
+static gboolean
+m365_transport_disconnect_sync (CamelService *service,
+ gboolean clean,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Transport *m365_transport = CAMEL_M365_TRANSPORT (service);
+ EM365Connection *cnc;
+ gboolean success = TRUE;
+
+ cnc = m365_transport_ref_connection (m365_transport);
+
+ if (cnc) {
+ success = e_m365_connection_disconnect_sync (cnc, cancellable, error);
+
+ g_clear_object (&cnc);
+ }
+
+ if (!success)
+ return FALSE;
+
+ /* Chain up to parent's method. */
+ return CAMEL_SERVICE_CLASS (camel_m365_transport_parent_class)->disconnect_sync (service, clean,
cancellable, error);
+}
+
+static CamelAuthenticationResult
+m365_transport_authenticate_sync (CamelService *service,
+ const gchar *mechanism,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelAuthenticationResult result;
+ CamelM365Transport *m365_transport;
+ EM365Connection *cnc;
+
+ m365_transport = CAMEL_M365_TRANSPORT (service);
+ cnc = m365_transport_ref_connection (m365_transport);
+
+ if (!cnc)
+ return CAMEL_AUTHENTICATION_ERROR;
+
+ switch (e_m365_connection_authenticate_sync (cnc, NULL, E_M365_FOLDER_KIND_MAIL, NULL, NULL, NULL,
NULL, cancellable, error)) {
+ case E_SOURCE_AUTHENTICATION_ERROR:
+ case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
+ default:
+ result = CAMEL_AUTHENTICATION_ERROR;
+ break;
+ case E_SOURCE_AUTHENTICATION_ACCEPTED:
+ result = CAMEL_AUTHENTICATION_ACCEPTED;
+ break;
+ case E_SOURCE_AUTHENTICATION_REJECTED:
+ case E_SOURCE_AUTHENTICATION_REQUIRED:
+ result = CAMEL_AUTHENTICATION_REJECTED;
+ break;
+ }
+
+ g_clear_object (&cnc);
+
+ return result;
+}
+
+static gchar *
+m365_transport_get_name (CamelService *service,
+ gboolean brief)
+{
+ gchar *name;
+
+ if (brief)
+ name = g_strdup (_("Microsoft 365 server"));
+ else
+ name = g_strdup (_("Mail delivery via Microsoft 365"));
+
+ return name;
+}
+
+static gboolean
+m365_send_to_sync (CamelTransport *transport,
+ CamelMimeMessage *message,
+ CamelAddress *from,
+ CamelAddress *recipients,
+ gboolean *out_sent_message_saved,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelInternetAddress *use_from;
+ CamelService *service;
+ EM365Connection *cnc;
+ JsonBuilder *builder;
+ gchar *appended_id = NULL;
+ gboolean is_server_side_sent;
+ gboolean success = FALSE;
+
+ service = CAMEL_SERVICE (transport);
+
+ if (CAMEL_IS_INTERNET_ADDRESS (from))
+ use_from = CAMEL_INTERNET_ADDRESS (from);
+ else
+ use_from = camel_mime_message_get_from (message);
+
+ if (!use_from || camel_address_length (CAMEL_ADDRESS (use_from)) == 0) {
+ g_set_error_literal (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Cannot send message with no From address"));
+ return FALSE;
+
+ } else if (camel_address_length (CAMEL_ADDRESS (use_from)) > 1) {
+ g_set_error_literal (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Microsoft 365 server cannot send message with multiple From addresses"));
+ return FALSE;
+
+ } else {
+ const gchar *used_email = NULL;
+
+ if (!camel_internet_address_get (use_from, 0, NULL, &used_email)) {
+ g_set_error_literal (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Failed to read From address"));
+ return FALSE;
+ }
+ }
+
+ cnc = m365_transport_ref_connection (CAMEL_M365_TRANSPORT (service));
+
+ if (!cnc) {
+ g_set_error_literal (
+ error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_NOT_CONNECTED,
+ _("Service not connected"));
+ return FALSE;
+ }
+
+ is_server_side_sent = m365_transport_is_server_side_sent_folder (service, cancellable);
+
+ if (is_server_side_sent && out_sent_message_saved)
+ *out_sent_message_saved = TRUE;
+
+ builder = json_builder_new_immutable ();
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_begin_object_member (builder, "message");
+
+ success = camel_m365_utils_fill_message_object_sync (builder, message, NULL, from, recipients, TRUE,
NULL, cancellable, error);
+
+ e_m365_json_end_object_member (builder); /* message */
+
+ if (!is_server_side_sent)
+ e_m365_json_add_boolean_member (builder, "saveToSentItems", FALSE);
+
+ e_m365_json_end_object_member (builder);
+
+ success = success && e_m365_connection_send_mail_sync (cnc, NULL, builder, cancellable, error);
+
+ g_object_unref (cnc);
+ g_free (appended_id);
+
+ return success;
+}
+
+static void
+m365_transport_dispose (GObject *object)
+{
+ CamelM365Transport *m365_transport = CAMEL_M365_TRANSPORT (object);
+
+ LOCK (m365_transport);
+
+ g_clear_object (&m365_transport->priv->cnc);
+
+ UNLOCK (m365_transport);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_transport_parent_class)->dispose (object);
+}
+
+static void
+m365_transport_finalize (GObject *object)
+{
+ CamelM365Transport *m365_transport = CAMEL_M365_TRANSPORT (object);
+
+ g_mutex_clear (&m365_transport->priv->property_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_transport_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_transport_class_init (CamelM365TransportClass *class)
+{
+ GObjectClass *object_class;
+ CamelServiceClass *service_class;
+ CamelTransportClass *transport_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = m365_transport_dispose;
+ object_class->finalize = m365_transport_finalize;
+
+ service_class = CAMEL_SERVICE_CLASS (class);
+ service_class->settings_type = CAMEL_TYPE_M365_SETTINGS;
+ service_class->get_name = m365_transport_get_name;
+ service_class->connect_sync = m365_transport_connect_sync;
+ service_class->disconnect_sync = m365_transport_disconnect_sync;
+ service_class->authenticate_sync = m365_transport_authenticate_sync;
+
+ transport_class = CAMEL_TRANSPORT_CLASS (class);
+ transport_class->send_to_sync = m365_send_to_sync;
+}
+
+static void
+camel_m365_transport_init (CamelM365Transport *m365_transport)
+{
+ m365_transport->priv = camel_m365_transport_get_instance_private (m365_transport);
+
+ g_mutex_init (&m365_transport->priv->property_lock);
+}
diff --git a/src/Microsoft365/camel/camel-m365-transport.h b/src/Microsoft365/camel/camel-m365-transport.h
new file mode 100644
index 00000000..f25c5856
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-transport.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_TRANSPORT_H
+#define CAMEL_M365_TRANSPORT_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_TRANSPORT \
+ (camel_m365_transport_get_type ())
+#define CAMEL_M365_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_TRANSPORT, CamelM365Transport))
+#define CAMEL_M365_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_TRANSPORT, CamelM365TransportClass))
+#define CAMEL_IS_M365_TRANSPORT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_TRANSPORT))
+#define CAMEL_IS_M365_TRANSPORT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_TRANSPORT))
+#define CAMEL_M365_TRANSPORT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_TRANSPORT, CamelM365TransportClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelM365Transport CamelM365Transport;
+typedef struct _CamelM365TransportClass CamelM365TransportClass;
+typedef struct _CamelM365TransportPrivate CamelM365TransportPrivate;
+
+struct _CamelM365Transport {
+ CamelTransport parent;
+ CamelM365TransportPrivate *priv;
+};
+
+struct _CamelM365TransportClass {
+ CamelTransportClass parent_class;
+};
+
+GType camel_m365_transport_get_type (void);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_TRANSPORT_H */
diff --git a/src/Microsoft365/camel/camel-m365-utils.c b/src/Microsoft365/camel/camel-m365-utils.c
new file mode 100644
index 00000000..9d496693
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-utils.c
@@ -0,0 +1,1004 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <libemail-engine/libemail-engine.h>
+
+#include "camel-m365-utils.h"
+
+/* Unref with g_object_unref() when done with it */
+static ESource *
+camel_m365_utils_ref_corresponding_source (CamelService *service,
+ GCancellable *cancellable)
+{
+ ESourceRegistry *registry = NULL;
+ CamelSession *session;
+ ESource *source = NULL;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ session = camel_service_ref_session (service);
+ if (E_IS_MAIL_SESSION (session)) {
+ registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
+ if (registry)
+ g_object_ref (registry);
+ }
+
+ g_clear_object (&session);
+
+ if (!registry)
+ registry = e_source_registry_new_sync (cancellable, NULL);
+
+ if (registry) {
+ source = e_source_registry_ref_source (registry, camel_service_get_uid (service));
+
+ if (source) {
+ ESource *parent;
+
+ parent = e_source_registry_find_extension (registry, source,
E_SOURCE_EXTENSION_COLLECTION);
+
+ g_clear_object (&source);
+ source = parent;
+ }
+ }
+
+ g_clear_object (®istry);
+
+ return source;
+}
+
+EM365Connection *
+camel_m365_utils_new_connection (CamelService *service,
+ GCancellable *cancellable)
+{
+ CamelSettings *settings;
+ EM365Connection *cnc;
+ ESource *source;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ source = camel_m365_utils_ref_corresponding_source (service, cancellable);
+
+ if (!source)
+ return NULL;
+
+ settings = camel_service_ref_settings (service);
+
+ cnc = e_m365_connection_new (source, CAMEL_M365_SETTINGS (settings));
+
+ e_binding_bind_property (
+ service, "proxy-resolver",
+ cnc, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
+
+ g_clear_object (&settings);
+ g_clear_object (&source);
+
+ return cnc;
+}
+
+/* From Outlook name (which allows spaces) to Evolution name */
+gchar *
+camel_m365_utils_encode_category_name (const gchar *name)
+{
+ if (name && strchr (name, ' ')) {
+ GString *str;
+
+ str = g_string_sized_new (strlen (name) + 16);
+
+ while (*name) {
+ if (*name == '_')
+ g_string_append_c (str, '_');
+
+ g_string_append_c (str, *name == ' ' ? '_' : *name);
+
+ name++;
+ }
+
+ return g_string_free (str, FALSE);
+ }
+
+ return g_strdup (name);
+}
+
+/* From Evolution name to Outlook name (which allows spaces) */
+gchar *
+camel_m365_utils_decode_category_name (const gchar *flag)
+{
+ if (flag && strchr (flag, '_')) {
+ GString *str = g_string_sized_new (strlen (flag));
+
+ while (*flag) {
+ if (*flag == '_') {
+ if (flag[1] == '_') {
+ g_string_append_c (str, '_');
+ flag++;
+ } else {
+ g_string_append_c (str, ' ');
+ }
+ } else {
+ g_string_append_c (str, *flag);
+ }
+
+ flag++;
+ }
+
+ return g_string_free (str, FALSE);
+ }
+
+ return g_strdup (flag);
+}
+
+gboolean
+camel_m365_utils_is_system_user_flag (const gchar *name)
+{
+ if (!name)
+ return FALSE;
+
+ return g_str_equal (name, "receipt-handled") ||
+ g_str_equal (name, "$has-cal");
+}
+
+const gchar *
+camel_m365_utils_rename_label (const gchar *cat,
+ gboolean from_cat)
+{
+ gint ii;
+
+ /* This is a mapping from Outlook categories to
+ * Evolution labels based on the standard colors */
+ const gchar *labels[] = {
+ "Red category", "$Labelimportant",
+ "Orange category", "$Labelwork",
+ "Green category", "$Labelpersonal",
+ "Blue category", "$Labeltodo",
+ "Purple category", "$Labellater",
+ NULL, NULL
+ };
+
+ if (!cat || !*cat)
+ return "";
+
+ for (ii = 0; labels[ii]; ii += 2) {
+ if (from_cat) {
+ if (!g_ascii_strcasecmp (cat, labels[ii]))
+ return labels[ii + 1];
+ } else {
+ if (!g_ascii_strcasecmp (cat, labels[ii + 1]))
+ return labels[ii];
+ }
+ }
+
+ return cat;
+}
+
+static void
+m365_utils_add_address (JsonBuilder *builder,
+ CamelInternetAddress *addr,
+ void (* add_func) (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address))
+{
+ const gchar *name = NULL, *address = NULL;
+
+ g_return_if_fail (add_func != NULL);
+
+ if (!addr || camel_address_length (CAMEL_ADDRESS (addr)) < 1)
+ return;
+
+ if (camel_internet_address_get (addr, 0, &name, &address))
+ add_func (builder, name, address);
+}
+
+static void
+m365_utils_add_address_array (JsonBuilder *builder,
+ CamelInternetAddress *addr,
+ void (* begin_func) (JsonBuilder *builder),
+ void (* end_func) (JsonBuilder *builder),
+ GHashTable *known_recipients,
+ CamelAddress *expected_recipients)
+{
+ gint ii, len;
+ gboolean did_add = FALSE;
+
+ g_return_if_fail (begin_func != NULL);
+ g_return_if_fail (end_func != NULL);
+
+ if (!addr)
+ return;
+
+ len = camel_address_length (CAMEL_ADDRESS (addr));
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = NULL, *address = NULL;
+
+ if (camel_internet_address_get (addr, 0, &name, &address)) {
+ if (!did_add) {
+ did_add = TRUE;
+ begin_func (builder);
+ }
+
+ if (known_recipients && address && *address)
+ g_hash_table_add (known_recipients, (gpointer) address);
+
+ e_m365_add_recipient (builder, NULL, name, address);
+ }
+ }
+
+ if (known_recipients && expected_recipients && CAMEL_IS_INTERNET_ADDRESS (expected_recipients)) {
+ CamelInternetAddress *iaddr = CAMEL_INTERNET_ADDRESS (expected_recipients);
+
+ len = camel_address_length (expected_recipients);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = NULL, *address = NULL;
+
+ if (camel_internet_address_get (iaddr, 0, &name, &address) && address && *address &&
+ !g_hash_table_contains (known_recipients, address)) {
+ if (!did_add) {
+ did_add = TRUE;
+ begin_func (builder);
+ }
+
+ if (known_recipients && address && *address)
+ g_hash_table_add (known_recipients, (gpointer) address);
+
+ e_m365_add_recipient (builder, NULL, name, address);
+ }
+ }
+ }
+
+ if (did_add)
+ end_func (builder);
+}
+
+static void
+m365_utils_add_headers (JsonBuilder *builder,
+ const CamelNameValueArray *headers,
+ CamelInternetAddress **out_sender,
+ gboolean *out_request_read_receipt)
+{
+ guint ii, len;
+ gint did_add = 0;
+
+ if (!headers)
+ return;
+
+ len = camel_name_value_array_get_length (headers);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = NULL, *value = NULL;
+
+ if (camel_name_value_array_get (headers, ii, &name, &value) && name && *name && value &&
+ g_ascii_strcasecmp (name, "X-Evolution-Source") != 0) {
+ /* The Graph API allows only X- headers to be saved */
+ if (g_ascii_strncasecmp (name, "X-", 2) == 0) {
+ if (!did_add)
+ e_m365_mail_message_begin_internet_message_headers (builder);
+
+ did_add++;
+
+ /* Preserve only the first five... (see the comment at
m365_folder_append_message_sync()) */
+ if (did_add < 5)
+ e_m365_add_internet_message_header (builder, name, value);
+ }
+
+ if (out_sender && g_ascii_strcasecmp (name, "Sender") == 0) {
+ CamelInternetAddress *addr;
+
+ addr = camel_internet_address_new ();
+
+ if (camel_address_decode (CAMEL_ADDRESS (addr), value) != -1) {
+ *out_sender = addr;
+ addr = NULL;
+ }
+
+ g_clear_object (&addr);
+
+ /* To not compare the header name again */
+ out_sender = NULL;
+ } else if (out_request_read_receipt && g_ascii_strcasecmp (name,
"Disposition-Notification-To") == 0) {
+ *out_request_read_receipt = TRUE;
+
+ /* To not compare the header name again */
+ out_request_read_receipt = NULL;
+ }
+ }
+ }
+
+ if (did_add)
+ e_m365_mail_message_end_internet_message_headers (builder);
+}
+
+static CamelStream *
+m365_utils_get_content_stream (CamelMimePart *part,
+ gssize *out_wrote_bytes,
+ GCancellable *cancellable)
+{
+ CamelStream *content_stream;
+ CamelStream *filter_stream = NULL;
+ CamelMimeFilterWindows *windows = NULL;
+ CamelDataWrapper *dw;
+ gssize wrote_bytes;
+
+ g_return_val_if_fail (part != NULL, NULL);
+
+ dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+ g_return_val_if_fail (dw != NULL, NULL);
+
+ content_stream = camel_stream_mem_new ();
+
+ if (camel_mime_part_get_content_type (part)) {
+ const gchar *charset = camel_content_type_param (camel_mime_part_get_content_type (part),
"charset");
+
+ if (charset && *charset && g_ascii_strcasecmp (charset, "utf8") != 0 && g_ascii_strcasecmp
(charset, "utf-8") != 0) {
+ if (g_ascii_strncasecmp (charset, "iso-8859-", 9) == 0) {
+ CamelStream *null;
+
+ /* Since a few Windows mailers like to claim they sent
+ * out iso-8859-# encoded text when they really sent
+ * out windows-cp125#, do some simple sanity checking
+ * before we move on... */
+
+ null = camel_stream_null_new ();
+ filter_stream = camel_stream_filter_new (null);
+ g_object_unref (null);
+
+ windows = (CamelMimeFilterWindows *)camel_mime_filter_windows_new (charset);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream),
CAMEL_MIME_FILTER (windows));
+
+ camel_data_wrapper_decode_to_stream_sync (dw, CAMEL_STREAM (filter_stream),
cancellable, NULL);
+ camel_stream_flush (CAMEL_STREAM (filter_stream), cancellable, NULL);
+ g_object_unref (filter_stream);
+
+ charset = camel_mime_filter_windows_real_charset (windows);
+ }
+
+ if (charset && *charset) {
+ CamelMimeFilter *filter;
+
+ filter_stream = camel_stream_filter_new (content_stream);
+
+ if ((filter = camel_mime_filter_charset_new (charset, "UTF-8"))) {
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream),
CAMEL_MIME_FILTER (filter));
+ g_object_unref (filter);
+ } else {
+ g_object_unref (filter_stream);
+ filter_stream = NULL;
+ }
+ }
+ }
+ }
+
+ if (filter_stream) {
+ wrote_bytes = camel_data_wrapper_decode_to_stream_sync (dw, CAMEL_STREAM (filter_stream),
cancellable, NULL);
+ camel_stream_flush (filter_stream, cancellable, NULL);
+ g_object_unref (filter_stream);
+ } else {
+ wrote_bytes = camel_data_wrapper_decode_to_stream_sync (dw, CAMEL_STREAM (content_stream),
cancellable, NULL);
+ }
+
+ if (windows)
+ g_object_unref (windows);
+
+ g_seekable_seek (G_SEEKABLE (content_stream), 0, G_SEEK_SET, NULL, NULL);
+
+ if (out_wrote_bytes)
+ *out_wrote_bytes = wrote_bytes;
+
+ return content_stream;
+}
+
+static gboolean
+m365_utils_part_is_attachment (CamelMimePart *part,
+ gboolean *out_is_inline)
+{
+ const CamelContentDisposition *content_disposition;
+
+ g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE);
+
+ content_disposition = camel_mime_part_get_content_disposition (part);
+
+ if (!content_disposition)
+ return FALSE;
+
+ if (out_is_inline) {
+ *out_is_inline = content_disposition && content_disposition->disposition &&
+ g_ascii_strcasecmp (content_disposition->disposition, "inline") == 0;
+ }
+
+ return content_disposition &&
+ content_disposition->disposition && (
+ g_ascii_strcasecmp (content_disposition->disposition, "attachment") == 0 ||
+ g_ascii_strcasecmp (content_disposition->disposition, "inline") == 0);
+}
+
+enum {
+ ADD_ATTACHMENT_WITH_CONTENT_TYPE = 1 << 0,
+ ADD_ATTACHMENT_PREFIX_CONTENT_TYPE_HEADER = 1 << 1,
+ ADD_ATTACHMENT_DECODE_CONTENT = 1 << 2
+};
+
+static void
+m365_utils_add_file_attachment_content (JsonBuilder *builder,
+ CamelDataWrapper *dw,
+ guint32 add_flags,
+ GCancellable *cancellable)
+{
+ CamelMimeFilter *filter;
+ CamelStream *content_stream, *filter_stream;
+ GByteArray *data;
+ CamelContentType *ct;
+ gchar *content_type_str;
+ const gchar *content_id;
+ gboolean is_inline = FALSE;
+
+ ct = camel_data_wrapper_get_mime_type_field (dw);
+ content_type_str = camel_content_type_format (ct);
+
+ if ((add_flags & ADD_ATTACHMENT_WITH_CONTENT_TYPE) != 0)
+ e_m365_attachment_add_content_type (builder, content_type_str);
+
+ content_stream = camel_stream_mem_new ();
+ filter_stream = camel_stream_filter_new (content_stream);
+
+ filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_BASE64_ENC);
+ camel_stream_filter_add (CAMEL_STREAM_FILTER (filter_stream), filter);
+ g_object_unref (filter);
+
+ if ((add_flags & ADD_ATTACHMENT_PREFIX_CONTENT_TYPE_HEADER) != 0) {
+ gchar *content_type_unfolded;
+
+ content_type_unfolded = camel_header_unfold (content_type_str);
+
+ #define wstr(str) camel_stream_write (filter_stream, str, strlen (str), cancellable, NULL)
+ wstr ("Content-Type: ");
+ wstr (content_type_unfolded);
+ wstr ("\r\n\r\n");
+ #undef wstr
+
+ g_free (content_type_unfolded);
+ }
+
+ g_free (content_type_str);
+
+ if (CAMEL_IS_MIME_PART (dw)) {
+ CamelMimePart *part = CAMEL_MIME_PART (dw);
+
+ content_id = camel_mime_part_get_content_id (part);
+ if (content_id)
+ e_m365_file_attachment_add_content_id (builder, content_id);
+
+ if (m365_utils_part_is_attachment (part, &is_inline) && is_inline)
+ e_m365_attachment_add_is_inline (builder, TRUE);
+
+ dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+ }
+
+ if ((add_flags & ADD_ATTACHMENT_DECODE_CONTENT) != 0)
+ camel_data_wrapper_decode_to_stream_sync (dw, filter_stream, cancellable, NULL);
+ else
+ camel_data_wrapper_write_to_stream_sync (dw, filter_stream, cancellable, NULL);
+ camel_stream_flush (filter_stream, cancellable, NULL);
+ g_object_unref (filter_stream);
+
+ camel_stream_flush (content_stream, cancellable, NULL);
+
+ data = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (content_stream));
+
+ /* Ensure the string is NUL-terminated */
+ g_byte_array_append (data, (const guchar *) "\0", 1);
+
+ e_m365_file_attachment_add_content_bytes (builder, (const gchar *) data->data);
+
+ g_object_unref (content_stream);
+}
+
+static void
+m365_utils_add_file_attachment (JsonBuilder *builder,
+ CamelDataWrapper *dw,
+ GCancellable *cancellable)
+{
+ const gchar *filename = NULL;
+
+ g_return_if_fail (builder != NULL);
+ g_return_if_fail (dw != NULL);
+
+ m365_utils_add_file_attachment_content (builder, dw, ADD_ATTACHMENT_WITH_CONTENT_TYPE |
ADD_ATTACHMENT_DECODE_CONTENT, cancellable);
+
+ if (CAMEL_IS_MIME_PART (dw))
+ filename = camel_mime_part_get_filename (CAMEL_MIME_PART (dw));
+
+ if (filename)
+ e_m365_attachment_add_name (builder, filename);
+ else
+ e_m365_attachment_add_name (builder, "attachment.dat");
+}
+
+static void
+m365_utils_add_smime_encrypted_attachment (JsonBuilder *builder,
+ CamelDataWrapper *dw,
+ GCancellable *cancellable)
+{
+ g_return_if_fail (builder != NULL);
+ g_return_if_fail (dw != NULL);
+
+ e_m365_attachment_add_name (builder, "smime.p7m");
+
+ m365_utils_add_file_attachment_content (builder, dw, ADD_ATTACHMENT_WITH_CONTENT_TYPE |
ADD_ATTACHMENT_DECODE_CONTENT, cancellable);
+}
+
+static void
+m365_utils_add_smime_signed_attachment (JsonBuilder *builder,
+ CamelDataWrapper *dw,
+ GCancellable *cancellable)
+{
+ e_m365_attachment_add_content_type (builder, "multipart/signed");
+ e_m365_attachment_add_name (builder, "smime.txt");
+
+ m365_utils_add_file_attachment_content (builder, dw, ADD_ATTACHMENT_PREFIX_CONTENT_TYPE_HEADER,
cancellable);
+}
+
+static gboolean
+m365_utils_do_smime_signed (CamelMultipart *multipart,
+ CamelMimePart **out_body_part,
+ GSList **out_attachments,
+ GCancellable *cancellable)
+{
+ CamelMimePart *content, *signature;
+ /*CamelContentType *ct;*/
+
+ content = camel_multipart_get_part (multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
+ signature = camel_multipart_get_part (multipart, CAMEL_MULTIPART_SIGNED_SIGNATURE);
+
+ g_return_val_if_fail (content != NULL, FALSE);
+ g_return_val_if_fail (signature != NULL, FALSE);
+
+ /*ct = camel_mime_part_get_content_type (content);
+
+ if (camel_content_type_is (ct, "text", "plain")) {
+ g_clear_object (out_body_part);
+ *out_body_part = g_object_ref (content);
+ } else if (camel_content_type_is (ct, "text", "html")) {
+ g_clear_object (out_body_part);
+ *out_body_part = g_object_ref (content);
+ } else {
+ *out_attachments = g_slist_prepend (*out_attachments, g_object_ref (content));
+ }*/
+
+ *out_attachments = g_slist_prepend (*out_attachments, g_object_ref (multipart));
+
+ return TRUE;
+}
+
+static gboolean
+m365_utils_do_multipart (CamelMultipart *mp,
+ gboolean *is_first,
+ CamelMimePart **out_body_part,
+ GSList **out_attachments,
+ GCancellable *cancellable)
+{
+ CamelDataWrapper *dw;
+ CamelContentType *type;
+ CamelMimePart *part;
+ gboolean parent_is_alternative;
+ gint nn, ii;
+
+ g_return_val_if_fail (is_first != NULL, FALSE);
+
+ type = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (mp));
+ parent_is_alternative = type && camel_content_type_is (type, "multipart", "alternative");
+
+ nn = camel_multipart_get_number (mp);
+
+ for (ii = 0; ii < nn; ii++) {
+ part = camel_multipart_get_part (mp, ii);
+
+ if (!part)
+ continue;
+
+ dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+
+ if (CAMEL_IS_MULTIPART (dw)) {
+ if (!m365_utils_do_multipart (CAMEL_MULTIPART (dw), is_first, out_body_part,
out_attachments, cancellable))
+ return FALSE;
+ continue;
+ }
+
+ type = camel_mime_part_get_content_type (part);
+
+ if (ii == 0 && (*is_first) && camel_content_type_is (type, "text", "plain")) {
+ g_clear_object (out_body_part);
+ *out_body_part = g_object_ref (part);
+
+ *is_first = FALSE;
+ } else if ((ii == 0 || parent_is_alternative) &&
+ camel_content_type_is (type, "text", "html") &&
+ !m365_utils_part_is_attachment (part, NULL)) {
+ g_clear_object (out_body_part);
+ *out_body_part = g_object_ref (part);
+ } else {
+ *out_attachments = g_slist_prepend (*out_attachments, g_object_ref (part));
+ }
+ }
+
+ return TRUE;
+}
+
+static CamelMimePart *
+m365_utils_get_body_part (CamelMimeMessage *message,
+ GSList **out_attachments,
+ GCancellable *cancellable)
+{
+ CamelContentType *ct;
+ CamelMimePart *body_part = NULL;
+
+ ct = camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (message));
+ g_return_val_if_fail (ct != NULL, NULL);
+
+ if (camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
+ camel_content_type_is (ct, "application", "pkcs7-mime")) {
+ *out_attachments = g_slist_prepend (*out_attachments, g_object_ref (message));
+ } else {
+ CamelDataWrapper *dw = NULL;
+ CamelMultipart *multipart;
+
+ /* contents body */
+ dw = camel_medium_get_content (CAMEL_MEDIUM (message));
+
+ if (CAMEL_IS_MULTIPART (dw)) {
+ gboolean is_first = TRUE;
+
+ multipart = CAMEL_MULTIPART (dw);
+ if (CAMEL_IS_MULTIPART_SIGNED (multipart) && camel_multipart_get_number (multipart)
== 2) {
+ m365_utils_do_smime_signed (multipart, &body_part, out_attachments,
cancellable);
+ } else {
+ m365_utils_do_multipart (multipart, &is_first, &body_part, out_attachments,
cancellable);
+ }
+ } else if (dw) {
+ CamelContentType *type;
+ CamelMimePart *part = CAMEL_MIME_PART (message);
+
+ type = camel_data_wrapper_get_mime_type_field (dw);
+
+ if (camel_content_type_is (type, "text", "plain")) {
+ body_part = g_object_ref (part);
+ } else if (camel_content_type_is (type, "text", "html")) {
+ body_part = g_object_ref (part);
+ } else {
+ *out_attachments = g_slist_prepend (*out_attachments, g_object_ref (part));
+ }
+ }
+ }
+
+ *out_attachments = g_slist_reverse (*out_attachments);
+
+ return body_part;
+}
+
+void
+camel_m365_utils_add_message_flags (JsonBuilder *builder,
+ CamelMessageInfo *info,
+ CamelMimeMessage *message)
+{
+ guint32 flags = 0;
+
+ if (info) {
+ const CamelNamedFlags *user_flags;
+ gboolean did_add = FALSE;
+ guint ii, len;
+
+ flags = camel_message_info_get_flags (info);
+
+ user_flags = camel_message_info_get_user_flags (info);
+ len = camel_named_flags_get_length (user_flags);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *name = camel_named_flags_get (user_flags, ii);
+
+ if (!camel_m365_utils_is_system_user_flag (name)) {
+ const gchar *renamed;
+
+ renamed = camel_m365_utils_rename_label (name, FALSE);
+
+ if (renamed && *renamed && renamed != name) {
+ if (!did_add) {
+ did_add = TRUE;
+ e_m365_mail_message_begin_categories (builder);
+ }
+
+ e_m365_mail_message_add_category (builder, renamed);
+ } else if (renamed == name && name && *name) {
+ gchar *cat;
+
+ cat = camel_m365_utils_decode_category_name (name);
+
+ if (cat && *cat) {
+ if (!did_add) {
+ did_add = TRUE;
+ e_m365_mail_message_begin_categories (builder);
+ }
+
+ e_m365_mail_message_add_category (builder, cat);
+ }
+
+ g_free (cat);
+ }
+ }
+ }
+
+ if (did_add)
+ e_m365_mail_message_end_categories (builder);
+ }
+
+ if (message && !(flags & CAMEL_MESSAGE_FLAGGED)) {
+ CamelMedium *medium = CAMEL_MEDIUM (message);
+ const gchar *value;
+
+ value = camel_medium_get_header (medium, "X-Priority");
+
+ if (g_strcmp0 (value, "1") == 0) {
+ flags |= CAMEL_MESSAGE_FLAGGED;
+ } else {
+ value = camel_medium_get_header (medium, "Importance");
+
+ if (value && g_ascii_strcasecmp (value, "High") == 0)
+ flags |= CAMEL_MESSAGE_FLAGGED;
+ }
+ }
+
+ e_m365_mail_message_add_importance (builder,
+ (flags & CAMEL_MESSAGE_FLAGGED) != 0 ? E_M365_IMPORTANCE_HIGH : E_M365_IMPORTANCE_NORMAL);
+
+ e_m365_mail_message_add_is_read (builder, (flags & CAMEL_MESSAGE_SEEN) != 0);
+}
+
+static void
+m365_utils_add_attachment_object (JsonBuilder *builder,
+ CamelDataWrapper *dw,
+ GCancellable *cancellable)
+{
+ CamelContentType *ct;
+
+ ct = camel_data_wrapper_get_mime_type_field (dw);
+
+ e_m365_attachment_begin_attachment (builder, E_M365_ATTACHMENT_DATA_TYPE_FILE);
+
+ if (camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
+ camel_content_type_is (ct, "application", "pkcs7-mime")) {
+ m365_utils_add_smime_encrypted_attachment (builder, dw, cancellable);
+ } else if (CAMEL_IS_MULTIPART_SIGNED (dw)) {
+ m365_utils_add_smime_signed_attachment (builder, dw, cancellable);
+ } else {
+ m365_utils_add_file_attachment (builder, dw, cancellable);
+ }
+
+ e_m365_json_end_object_member (builder);
+}
+
+gboolean
+camel_m365_utils_fill_message_object_sync (JsonBuilder *builder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ CamelAddress *override_from,
+ CamelAddress *override_recipients, /* it merges them, not really
override */
+ gboolean is_send,
+ GSList **out_attachments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelInternetAddress *addr, *sender = NULL;
+ CamelMimePart *body_part;
+ GHashTable *known_recipients = NULL;
+ GSList *attachments = NULL;
+ time_t tt;
+ gint offset = 0;
+ const gchar *tmp;
+ gboolean success = TRUE, request_read_receipt = FALSE;
+
+ g_return_val_if_fail (builder != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ tmp = camel_mime_message_get_message_id (message);
+ if (tmp && *tmp)
+ e_m365_mail_message_add_internet_message_id (builder, tmp);
+
+ tmp = camel_mime_message_get_subject (message);
+ e_m365_mail_message_add_subject (builder, tmp ? tmp : "");
+
+ tt = camel_mime_message_get_date (message, &offset);
+
+ if (tt > (time_t) 0) {
+ /* Convert to UTC */
+ tt += (offset / 100) * 60 * 60;
+ tt += (offset % 100) * 60;
+
+ e_m365_mail_message_add_sent_date_time (builder, tt);
+ }
+
+ offset = 0;
+ tt = camel_mime_message_get_date_received (message, &offset);
+
+ if (tt > (time_t) 0) {
+ /* Convert to UTC */
+ tt += (offset / 100) * 60 * 60;
+ tt += (offset % 100) * 60;
+
+ e_m365_mail_message_add_received_date_time (builder, tt);
+ }
+
+ if (override_recipients)
+ known_recipients = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+
+ if (override_from && CAMEL_IS_INTERNET_ADDRESS (override_from))
+ addr = CAMEL_INTERNET_ADDRESS (override_from);
+ else
+ addr = camel_mime_message_get_from (message);
+ m365_utils_add_address (builder, addr, e_m365_mail_message_add_from);
+
+ addr = camel_mime_message_get_reply_to (message);
+ m365_utils_add_address_array (builder, addr, e_m365_mail_message_begin_reply_to,
e_m365_mail_message_end_reply_to, NULL, NULL);
+
+ addr = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
+ m365_utils_add_address_array (builder, addr, e_m365_mail_message_begin_to_recipients,
e_m365_mail_message_end_to_recipients, known_recipients, NULL);
+
+ addr = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
+ m365_utils_add_address_array (builder, addr, e_m365_mail_message_begin_cc_recipients,
e_m365_mail_message_end_cc_recipients, known_recipients, NULL);
+
+ addr = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
+ m365_utils_add_address_array (builder, addr, e_m365_mail_message_begin_bcc_recipients,
e_m365_mail_message_end_bcc_recipients, known_recipients, override_recipients);
+
+ if (known_recipients) {
+ g_hash_table_destroy (known_recipients);
+ known_recipients = NULL;
+ }
+
+ m365_utils_add_headers (builder, camel_medium_get_headers (CAMEL_MEDIUM (message)), &sender,
&request_read_receipt);
+
+ if (sender) {
+ const gchar *name = NULL, *address = NULL;
+
+ if (camel_internet_address_get (sender, 0, &name, &address) && ((name && *name) || (address
&& *address)))
+ e_m365_mail_message_add_sender (builder, name, address);
+
+ g_clear_object (&sender);
+ } else if (override_from) {
+ /* Possibly force the Sender when the passed-in From doesn't match the account user address */
+ }
+
+ if (request_read_receipt)
+ e_m365_mail_message_add_is_read_receipt_requested (builder, TRUE);
+
+ body_part = m365_utils_get_body_part (message, &attachments, cancellable);
+
+ if (body_part) {
+ CamelContentType *ct;
+ EM365ItemBodyContentTypeType m365_content_type = E_M365_ITEM_BODY_CONTENT_TYPE_UNKNOWN;
+
+ ct = camel_mime_part_get_content_type (body_part);
+
+ if (ct && camel_content_type_is (ct, "text", "html"))
+ m365_content_type = E_M365_ITEM_BODY_CONTENT_TYPE_HTML;
+ else if (ct && camel_content_type_is (ct, "text", "plain"))
+ m365_content_type = E_M365_ITEM_BODY_CONTENT_TYPE_TEXT;
+
+ if (m365_content_type != E_M365_ITEM_BODY_CONTENT_TYPE_UNKNOWN) {
+ CamelStream *mem;
+ gssize wrote = -1;
+
+ mem = m365_utils_get_content_stream (body_part, &wrote, cancellable);
+
+ if (mem && wrote >= 0) {
+ GByteArray *byte_array;
+
+ camel_stream_flush (mem, cancellable, NULL);
+
+ byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
+
+ /* Ensure the string is NUL-terminated */
+ g_byte_array_append (byte_array, (const guchar *) "\0", 1);
+
+ e_m365_mail_message_add_body (builder, m365_content_type, (const gchar *)
byte_array->data);
+ }
+
+ g_clear_object (&mem);
+ }
+
+ g_object_unref (body_part);
+ } else {
+ e_m365_json_add_null_member (builder, "body");
+ }
+
+ if (info || is_send)
+ camel_m365_utils_add_message_flags (builder, info, is_send ? message : NULL);
+
+ if (out_attachments) {
+ *out_attachments = attachments;
+ } else if (attachments) {
+ GSList *link;
+
+ e_m365_json_begin_array_member (builder, "attachments");
+
+ for (link = attachments; link && success; link = g_slist_next (link)) {
+ CamelDataWrapper *dw = link->data;
+
+ m365_utils_add_attachment_object (builder, dw, cancellable);
+ }
+
+ e_m365_json_end_array_member (builder);
+
+ g_slist_free_full (attachments, g_object_unref);
+ }
+
+ return success;
+}
+
+gboolean
+camel_m365_utils_create_message_sync (EM365Connection *cnc,
+ const gchar *folder_id,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **out_appended_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365MailMessage *appended_message = NULL;
+ GSList *attachments = NULL;
+ JsonBuilder *builder;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+
+ if (!camel_m365_utils_fill_message_object_sync (builder, message, info, NULL, NULL, FALSE,
&attachments, cancellable, error)) {
+ g_slist_free_full (attachments, g_object_unref);
+ g_object_unref (builder);
+
+ return FALSE;
+ }
+
+ e_m365_json_end_object_member (builder);
+
+ success = e_m365_connection_create_mail_message_sync (cnc, NULL, folder_id, builder,
&appended_message, cancellable, error);
+
+ g_warn_if_fail ((success && appended_message) || (!success && !appended_message));
+
+ g_object_unref (builder);
+
+ if (success && appended_message) {
+ GSList *link;
+ const gchar *message_id;
+
+ message_id = e_m365_mail_message_get_id (appended_message);
+
+ if (out_appended_id)
+ *out_appended_id = g_strdup (message_id);
+
+ for (link = attachments; link && success; link = g_slist_next (link)) {
+ CamelDataWrapper *dw = link->data;
+
+ builder = json_builder_new_immutable ();
+
+ m365_utils_add_attachment_object (builder, dw, cancellable);
+
+ success = e_m365_connection_add_mail_message_attachment_sync (cnc, NULL, message_id,
builder, NULL, cancellable, error);
+
+ g_object_unref (builder);
+ }
+ }
+
+ g_slist_free_full (attachments, g_object_unref);
+
+ if (appended_message)
+ json_object_unref (appended_message);
+
+ return success;
+}
diff --git a/src/Microsoft365/camel/camel-m365-utils.h b/src/Microsoft365/camel/camel-m365-utils.h
new file mode 100644
index 00000000..aff72a3a
--- /dev/null
+++ b/src/Microsoft365/camel/camel-m365-utils.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_UTILS_H
+#define CAMEL_M365_UTILS_H
+
+#include <camel/camel.h>
+
+#include "common/e-m365-connection.h"
+
+EM365Connection *
+ camel_m365_utils_new_connection (CamelService *service,
+ GCancellable *cancellable);
+gchar * camel_m365_utils_encode_category_name
+ (const gchar *name);
+gchar * camel_m365_utils_decode_category_name
+ (const gchar *flag);
+gboolean camel_m365_utils_is_system_user_flag
+ (const gchar *name);
+const gchar * camel_m365_utils_rename_label (const gchar *cat,
+ gboolean from_cat);
+void camel_m365_utils_add_message_flags
+ (JsonBuilder *builder,
+ CamelMessageInfo *info,
+ CamelMimeMessage *message);
+gboolean camel_m365_utils_fill_message_object_sync
+ (JsonBuilder *builder,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ CamelAddress *override_from,
+ CamelAddress *override_recipients, /* it merges them, not
really override */
+ gboolean is_send,
+ GSList **out_attachments,
+ GCancellable *cancellable,
+ GError **error);
+gboolean camel_m365_utils_create_message_sync
+ (EM365Connection *cnc,
+ const gchar *folder_id,
+ CamelMimeMessage *message,
+ CamelMessageInfo *info,
+ gchar **out_appended_id,
+ GCancellable *cancellable,
+ GError **error);
+
+#endif /* CAMEL_M365_UTILS_H */
diff --git a/src/Microsoft365/camel/libcamelmicrosoft365.urls
b/src/Microsoft365/camel/libcamelmicrosoft365.urls
new file mode 100644
index 00000000..a74a6fc6
--- /dev/null
+++ b/src/Microsoft365/camel/libcamelmicrosoft365.urls
@@ -0,0 +1 @@
+microsoft365
diff --git a/src/Microsoft365/common/CMakeLists.txt b/src/Microsoft365/common/CMakeLists.txt
new file mode 100644
index 00000000..671ae495
--- /dev/null
+++ b/src/Microsoft365/common/CMakeLists.txt
@@ -0,0 +1,72 @@
+glib_mkenums(e-m365-enumtypes e-m365-enums.h E_M365_ENUMTYPES_H)
+
+set(SOURCES
+ camel-sasl-xoauth2-microsoft365.c
+ camel-sasl-xoauth2-microsoft365.h
+ camel-m365-settings.c
+ camel-m365-settings.h
+ e-m365-connection.c
+ e-m365-connection.h
+ e-m365-enums.h
+ e-m365-json-utils.c
+ e-m365-json-utils.h
+ e-m365-tz-utils.c
+ e-m365-tz-utils.h
+ e-oauth2-service-microsoft365.c
+ e-oauth2-service-microsoft365.h
+ e-source-m365-folder.c
+ e-source-m365-folder.h
+ ${CMAKE_CURRENT_BINARY_DIR}/e-m365-enumtypes.c
+ ${CMAKE_CURRENT_BINARY_DIR}/e-m365-enumtypes.h
+)
+
+add_library(evolution-microsoft365 SHARED
+ ${SOURCES}
+)
+
+target_compile_definitions(evolution-microsoft365 PRIVATE
+ -DG_LOG_DOMAIN=\"evolution-microsoft365\"
+ -DM365_DATADIR=\"${ewsdatadir}\"
+)
+
+target_compile_options(evolution-microsoft365 PUBLIC
+ ${CAMEL_CFLAGS}
+ ${EVOLUTION_CALENDAR_CFLAGS}
+ ${JSON_GLIB_CFLAGS}
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBECAL_CFLAGS}
+ ${LIBEDATACAL_CFLAGS}
+ ${LIBEDATASERVER_CFLAGS}
+ ${SOUP_CFLAGS}
+)
+
+target_include_directories(evolution-microsoft365 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/src/Microsoft365
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${EVOLUTION_CALENDAR_INCLUDE_DIRS}
+ ${JSON_GLIB_INCLUDE_DIRS}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBECAL_INCLUDE_DIRS}
+ ${LIBEDATACAL_INCLUDE_DIRS}
+ ${LIBEDATASERVER_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+)
+
+target_link_libraries(evolution-microsoft365
+ ${CAMEL_LDFLAGS}
+ ${EVOLUTION_CALENDAR_LDFLAGS}
+ ${JSON_GLIB_LDFLAGS}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBECAL_LDFLAGS}
+ ${LIBEDATACAL_LDFLAGS}
+ ${LIBEDATASERVER_LDFLAGS}
+ ${SOUP_LDFLAGS}
+)
+
+install(TARGETS evolution-microsoft365
+ DESTINATION ${privsolibdir}
+)
diff --git a/src/Microsoft365/common/camel-m365-settings.c b/src/Microsoft365/common/camel-m365-settings.c
new file mode 100644
index 00000000..daeac748
--- /dev/null
+++ b/src/Microsoft365/common/camel-m365-settings.c
@@ -0,0 +1,938 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <libebackend/libebackend.h>
+#include <libedataserver/libedataserver.h>
+
+#include "camel-m365-settings.h"
+
+struct _CamelM365SettingsPrivate {
+ GMutex property_lock;
+ gboolean use_impersonation;
+ gboolean check_all;
+ gboolean filter_junk;
+ gboolean filter_junk_inbox;
+ gboolean override_oauth2;
+ guint timeout;
+ guint concurrent_connections;
+ gchar *impersonate_user;
+ gchar *email;
+ gchar *oauth2_tenant;
+ gchar *oauth2_client_id;
+ gchar *oauth2_redirect_uri;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTH_MECHANISM,
+ PROP_CHECK_ALL,
+ PROP_EMAIL,
+ PROP_FILTER_JUNK,
+ PROP_FILTER_JUNK_INBOX,
+ PROP_HOST,
+ PROP_PORT,
+ PROP_SECURITY_METHOD,
+ PROP_TIMEOUT,
+ PROP_USER,
+ PROP_USE_IMPERSONATION,
+ PROP_IMPERSONATE_USER,
+ PROP_OVERRIDE_OAUTH2,
+ PROP_OAUTH2_TENANT,
+ PROP_OAUTH2_CLIENT_ID,
+ PROP_OAUTH2_REDIRECT_URI,
+ PROP_CONCURRENT_CONNECTIONS
+};
+
+G_DEFINE_TYPE_WITH_CODE (CamelM365Settings, camel_m365_settings, CAMEL_TYPE_OFFLINE_SETTINGS,
+ G_IMPLEMENT_INTERFACE (CAMEL_TYPE_NETWORK_SETTINGS, NULL)
+ G_ADD_PRIVATE (CamelM365Settings))
+
+static void
+m365_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ camel_network_settings_set_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_CHECK_ALL:
+ camel_m365_settings_set_check_all (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EMAIL:
+ camel_m365_settings_set_email (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_FILTER_JUNK:
+ camel_m365_settings_set_filter_junk (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_FILTER_JUNK_INBOX:
+ camel_m365_settings_set_filter_junk_inbox (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HOST:
+ camel_network_settings_set_host (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_PORT:
+ camel_network_settings_set_port (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ camel_network_settings_set_security_method (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_TIMEOUT:
+ camel_m365_settings_set_timeout (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_USER:
+ camel_network_settings_set_user (
+ CAMEL_NETWORK_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_USE_IMPERSONATION:
+ camel_m365_settings_set_use_impersonation (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_IMPERSONATE_USER:
+ camel_m365_settings_set_impersonate_user (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_OVERRIDE_OAUTH2:
+ camel_m365_settings_set_override_oauth2 (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_OAUTH2_TENANT:
+ camel_m365_settings_set_oauth2_tenant (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_OAUTH2_CLIENT_ID:
+ camel_m365_settings_set_oauth2_client_id (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_OAUTH2_REDIRECT_URI:
+ camel_m365_settings_set_oauth2_redirect_uri (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ camel_m365_settings_set_concurrent_connections (
+ CAMEL_M365_SETTINGS (object),
+ g_value_get_uint (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_AUTH_MECHANISM:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_auth_mechanism (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_CHECK_ALL:
+ g_value_set_boolean (
+ value,
+ camel_m365_settings_get_check_all (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_EMAIL:
+ g_value_take_string (
+ value,
+ camel_m365_settings_dup_email (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_JUNK:
+ g_value_set_boolean (
+ value,
+ camel_m365_settings_get_filter_junk (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_FILTER_JUNK_INBOX:
+ g_value_set_boolean (
+ value,
+ camel_m365_settings_get_filter_junk_inbox (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_HOST:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_host (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value,
+ camel_network_settings_get_port (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value,
+ camel_network_settings_get_security_method (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_TIMEOUT:
+ g_value_set_uint (
+ value,
+ camel_m365_settings_get_timeout (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_USER:
+ g_value_take_string (
+ value,
+ camel_network_settings_dup_user (
+ CAMEL_NETWORK_SETTINGS (object)));
+ return;
+
+ case PROP_USE_IMPERSONATION:
+ g_value_set_boolean (
+ value,
+ camel_m365_settings_get_use_impersonation (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_IMPERSONATE_USER:
+ g_value_take_string (
+ value,
+ camel_m365_settings_dup_impersonate_user (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_OVERRIDE_OAUTH2:
+ g_value_set_boolean (
+ value,
+ camel_m365_settings_get_override_oauth2 (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_OAUTH2_TENANT:
+ g_value_take_string (
+ value,
+ camel_m365_settings_dup_oauth2_tenant (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_OAUTH2_CLIENT_ID:
+ g_value_take_string (
+ value,
+ camel_m365_settings_dup_oauth2_client_id (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_OAUTH2_REDIRECT_URI:
+ g_value_take_string (
+ value,
+ camel_m365_settings_dup_oauth2_redirect_uri (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ g_value_set_uint (
+ value,
+ camel_m365_settings_get_concurrent_connections (
+ CAMEL_M365_SETTINGS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_settings_finalize (GObject *object)
+{
+ CamelM365Settings *m365_settings = CAMEL_M365_SETTINGS (object);
+
+ g_mutex_clear (&m365_settings->priv->property_lock);
+
+ g_free (m365_settings->priv->email);
+ g_free (m365_settings->priv->oauth2_tenant);
+ g_free (m365_settings->priv->oauth2_client_id);
+ g_free (m365_settings->priv->oauth2_redirect_uri);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (camel_m365_settings_parent_class)->finalize (object);
+}
+
+static void
+camel_m365_settings_class_init (CamelM365SettingsClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = m365_settings_set_property;
+ object_class->get_property = m365_settings_get_property;
+ object_class->finalize = m365_settings_finalize;
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_AUTH_MECHANISM,
+ "auth-mechanism");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHECK_ALL,
+ g_param_spec_boolean (
+ "check-all",
+ "Check All",
+ "Check all folders for new messages",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EMAIL,
+ g_param_spec_string (
+ "email",
+ "Email",
+ "Email",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_JUNK,
+ g_param_spec_boolean (
+ "filter-junk",
+ "Filter Junk",
+ "Whether to filter junk from all folders",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER_JUNK_INBOX,
+ g_param_spec_boolean (
+ "filter-junk-inbox",
+ "Filter Junk Inbox",
+ "Whether to filter junk from Inbox only",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_HOST,
+ "host");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_PORT,
+ "port");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TIMEOUT,
+ g_param_spec_uint (
+ "timeout",
+ "timeout",
+ "Connection timeout in seconds",
+ 0, G_MAXUINT, 120,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ "security-method");
+
+ /* Inherited from CamelNetworkSettings. */
+ g_object_class_override_property (
+ object_class,
+ PROP_USER,
+ "user");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_IMPERSONATION,
+ g_param_spec_boolean (
+ "use-impersonation",
+ "Use Impersonation",
+ "Use Impersonation",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IMPERSONATE_USER,
+ g_param_spec_string (
+ "impersonate-user",
+ "Impersonate User",
+ "Impersonate User",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OVERRIDE_OAUTH2,
+ g_param_spec_boolean (
+ "override-oauth2",
+ "Override OAuth2",
+ "Whether to override OAuth2 values",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OAUTH2_TENANT,
+ g_param_spec_string (
+ "oauth2-tenant",
+ "OAuth2 Tenant",
+ "OAuth2 Tenant to use, only if override-oauth2 is TRUE, otherwise the compile-time
value is used",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OAUTH2_CLIENT_ID,
+ g_param_spec_string (
+ "oauth2-client-id",
+ "OAuth2 Client ID",
+ "OAuth2 Client-ID to use, only if override-oauth2 is TRUE, otherwise the compile-time
value is used",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OAUTH2_REDIRECT_URI,
+ g_param_spec_string (
+ "oauth2-redirect-uri",
+ "OAuth2 Redirect URI",
+ "OAuth2 Redirect URI to use, only if override-oauth2 is TRUE, otherwise the
compile-time value is used",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONCURRENT_CONNECTIONS,
+ g_param_spec_uint (
+ "concurrent-connections",
+ "Concurrent Connections",
+ "Number of concurrent connections to use",
+ MIN_CONCURRENT_CONNECTIONS,
+ MAX_CONCURRENT_CONNECTIONS,
+ 1,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+camel_m365_settings_init (CamelM365Settings *settings)
+{
+ settings->priv = camel_m365_settings_get_instance_private (settings);
+
+ g_mutex_init (&settings->priv->property_lock);
+}
+
+/* transfer none. Checks the settings from the ESource extension related to backend. */
+CamelM365Settings *
+camel_m365_settings_get_from_backend (struct _EBackend *backend,
+ struct _ESourceRegistry *registry)
+{
+ ESource *source;
+ ESource *collection;
+ ESourceCamel *extension;
+ CamelSettings *settings;
+ const gchar *extension_name;
+
+ g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
+ if (registry)
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ source = e_backend_get_source (backend);
+ extension_name = e_source_camel_get_extension_name ("microsoft365");
+ e_source_camel_generate_subtype ("microsoft365", CAMEL_TYPE_M365_SETTINGS);
+
+ if (registry) {
+ /* It's either in the 'source' or in the collection parent. */
+ collection = e_source_registry_find_extension (registry, source, extension_name);
+
+ g_return_val_if_fail (collection != NULL, NULL);
+ } else {
+ collection = g_object_ref (source);
+ }
+
+ extension = e_source_get_extension (collection, extension_name);
+ settings = e_source_camel_get_settings (extension);
+
+ g_object_unref (collection);
+
+ return CAMEL_M365_SETTINGS (settings);
+}
+
+gboolean
+camel_m365_settings_get_use_impersonation (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), FALSE);
+
+ return settings->priv->use_impersonation;
+}
+
+void
+camel_m365_settings_set_use_impersonation (CamelM365Settings *settings,
+ gboolean use_impersonation)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if ((settings->priv->use_impersonation ? 1 : 0) == (use_impersonation ? 1 : 0))
+ return;
+
+ settings->priv->use_impersonation = use_impersonation;
+
+ g_object_notify (G_OBJECT (settings), "use-impersonation");
+}
+
+const gchar *
+camel_m365_settings_get_impersonate_user (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return settings->priv->impersonate_user;
+}
+
+gchar *
+camel_m365_settings_dup_impersonate_user (CamelM365Settings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_m365_settings_get_impersonate_user (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+void
+camel_m365_settings_set_impersonate_user (CamelM365Settings *settings,
+ const gchar *impersonate_user)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->impersonate_user, impersonate_user) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->impersonate_user);
+ settings->priv->impersonate_user = e_util_strdup_strip (impersonate_user);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "impersonate-user");
+}
+
+gboolean
+camel_m365_settings_get_check_all (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), FALSE);
+
+ return settings->priv->check_all;
+}
+
+void
+camel_m365_settings_set_check_all (CamelM365Settings *settings,
+ gboolean check_all)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if ((settings->priv->check_all ? 1 : 0) == (check_all ? 1 : 0))
+ return;
+
+ settings->priv->check_all = check_all;
+
+ g_object_notify (G_OBJECT (settings), "check-all");
+}
+
+const gchar *
+camel_m365_settings_get_email (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return settings->priv->email;
+}
+
+gchar *
+camel_m365_settings_dup_email (CamelM365Settings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_m365_settings_get_email (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+void
+camel_m365_settings_set_email (CamelM365Settings *settings,
+ const gchar *email)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->email, email) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->email);
+ settings->priv->email = e_util_strdup_strip (email);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "email");
+}
+
+gboolean
+camel_m365_settings_get_filter_junk (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_junk;
+}
+
+void
+camel_m365_settings_set_filter_junk (CamelM365Settings *settings,
+ gboolean filter_junk)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if ((settings->priv->filter_junk ? 1 : 0) == (filter_junk ? 1 : 0))
+ return;
+
+ settings->priv->filter_junk = filter_junk;
+
+ g_object_notify (G_OBJECT (settings), "filter-junk");
+}
+
+gboolean
+camel_m365_settings_get_filter_junk_inbox (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), FALSE);
+
+ return settings->priv->filter_junk_inbox;
+}
+
+void
+camel_m365_settings_set_filter_junk_inbox (CamelM365Settings *settings,
+ gboolean filter_junk_inbox)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if ((settings->priv->filter_junk_inbox ? 1 : 0) == (filter_junk_inbox ? 1 : 0))
+ return;
+
+ settings->priv->filter_junk_inbox = filter_junk_inbox;
+
+ g_object_notify (G_OBJECT (settings), "filter-junk-inbox");
+}
+
+guint
+camel_m365_settings_get_timeout (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), 0);
+
+ return settings->priv->timeout;
+}
+
+void
+camel_m365_settings_set_timeout (CamelM365Settings *settings,
+ guint timeout)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if (settings->priv->timeout == timeout)
+ return;
+
+ settings->priv->timeout = timeout;
+
+ g_object_notify (G_OBJECT (settings), "timeout");
+}
+
+gboolean
+camel_m365_settings_get_override_oauth2 (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), FALSE);
+
+ return settings->priv->override_oauth2;
+}
+
+void
+camel_m365_settings_set_override_oauth2 (CamelM365Settings *settings,
+ gboolean override_oauth2)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ if ((settings->priv->override_oauth2 ? 1 : 0) == (override_oauth2 ? 1 : 0))
+ return;
+
+ settings->priv->override_oauth2 = override_oauth2;
+
+ g_object_notify (G_OBJECT (settings), "override-oauth2");
+}
+
+const gchar *
+camel_m365_settings_get_oauth2_tenant (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return settings->priv->oauth2_tenant;
+}
+
+gchar *
+camel_m365_settings_dup_oauth2_tenant (CamelM365Settings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_m365_settings_get_oauth2_tenant (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+void
+camel_m365_settings_set_oauth2_tenant (CamelM365Settings *settings,
+ const gchar *tenant)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->oauth2_tenant, tenant) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->oauth2_tenant);
+ settings->priv->oauth2_tenant = e_util_strdup_strip (tenant);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "oauth2-tenant");
+}
+
+const gchar *
+camel_m365_settings_get_oauth2_client_id (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return settings->priv->oauth2_client_id;
+}
+
+gchar *
+camel_m365_settings_dup_oauth2_client_id (CamelM365Settings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_m365_settings_get_oauth2_client_id (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+void
+camel_m365_settings_set_oauth2_client_id (CamelM365Settings *settings,
+ const gchar *client_id)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->oauth2_client_id, client_id) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->oauth2_client_id);
+ settings->priv->oauth2_client_id = e_util_strdup_strip (client_id);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "oauth2-client-id");
+}
+
+const gchar *
+camel_m365_settings_get_oauth2_redirect_uri (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return settings->priv->oauth2_redirect_uri;
+}
+
+gchar *
+camel_m365_settings_dup_oauth2_redirect_uri (CamelM365Settings *settings)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ protected = camel_m365_settings_get_oauth2_redirect_uri (settings);
+ duplicate = g_strdup (protected);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ return duplicate;
+}
+
+void
+camel_m365_settings_set_oauth2_redirect_uri (CamelM365Settings *settings,
+ const gchar *redirect_uri)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ g_mutex_lock (&settings->priv->property_lock);
+
+ if (g_strcmp0 (settings->priv->oauth2_redirect_uri, redirect_uri) == 0) {
+ g_mutex_unlock (&settings->priv->property_lock);
+ return;
+ }
+
+ g_free (settings->priv->oauth2_redirect_uri);
+ settings->priv->oauth2_redirect_uri = e_util_strdup_strip (redirect_uri);
+
+ g_mutex_unlock (&settings->priv->property_lock);
+
+ g_object_notify (G_OBJECT (settings), "oauth2-redirect-uri");
+}
+
+guint
+camel_m365_settings_get_concurrent_connections (CamelM365Settings *settings)
+{
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), 1);
+
+ return settings->priv->concurrent_connections;
+}
+
+void
+camel_m365_settings_set_concurrent_connections (CamelM365Settings *settings,
+ guint concurrent_connections)
+{
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+
+ concurrent_connections = CLAMP (
+ concurrent_connections,
+ MIN_CONCURRENT_CONNECTIONS,
+ MAX_CONCURRENT_CONNECTIONS);
+
+ if (settings->priv->concurrent_connections == concurrent_connections)
+ return;
+
+ settings->priv->concurrent_connections = concurrent_connections;
+
+ g_object_notify (G_OBJECT (settings), "concurrent-connections");
+}
diff --git a/src/Microsoft365/common/camel-m365-settings.h b/src/Microsoft365/common/camel-m365-settings.h
new file mode 100644
index 00000000..b2c6835f
--- /dev/null
+++ b/src/Microsoft365/common/camel-m365-settings.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_M365_SETTINGS_H
+#define CAMEL_M365_SETTINGS_H
+
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_M365_SETTINGS \
+ (camel_m365_settings_get_type ())
+#define CAMEL_M365_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_M365_SETTINGS, CamelM365Settings))
+#define CAMEL_M365_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_M365_SETTINGS, CamelM365SettingsClass))
+#define CAMEL_IS_M365_SETTINGS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_M365_SETTINGS))
+#define CAMEL_IS_M365_SETTINGS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_M365_SETTINGS))
+#define CAMEL_M365_SETTINGS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_M365_SETTINGS))
+
+#define MIN_CONCURRENT_CONNECTIONS 1
+#define MAX_CONCURRENT_CONNECTIONS 7
+
+G_BEGIN_DECLS
+
+/* Forward declaration, to not include libedataserver.h/libebackend.h here */
+struct _EBackend;
+struct _ESourceRegistry;
+
+typedef struct _CamelM365Settings CamelM365Settings;
+typedef struct _CamelM365SettingsClass CamelM365SettingsClass;
+typedef struct _CamelM365SettingsPrivate CamelM365SettingsPrivate;
+
+struct _CamelM365Settings {
+ CamelOfflineSettings parent;
+ CamelM365SettingsPrivate *priv;
+};
+
+struct _CamelM365SettingsClass {
+ CamelOfflineSettingsClass parent_class;
+};
+
+GType camel_m365_settings_get_type (void) G_GNUC_CONST;
+CamelM365Settings *
+ camel_m365_settings_get_from_backend
+ (struct _EBackend *backend,
+ struct _ESourceRegistry *registry);
+gboolean camel_m365_settings_get_use_impersonation
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_use_impersonation
+ (CamelM365Settings *settings,
+ gboolean use_impersonation);
+const gchar * camel_m365_settings_get_impersonate_user
+ (CamelM365Settings *settings);
+gchar * camel_m365_settings_dup_impersonate_user
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_impersonate_user
+ (CamelM365Settings *settings,
+ const gchar *impersonate_user);
+gboolean camel_m365_settings_get_check_all
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_check_all
+ (CamelM365Settings *settings,
+ gboolean check_all);
+const gchar * camel_m365_settings_get_email (CamelM365Settings *settings);
+gchar * camel_m365_settings_dup_email (CamelM365Settings *settings);
+void camel_m365_settings_set_email (CamelM365Settings *settings,
+ const gchar *email);
+gboolean camel_m365_settings_get_filter_junk
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_filter_junk
+ (CamelM365Settings *settings,
+ gboolean filter_junk);
+gboolean camel_m365_settings_get_filter_junk_inbox
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_filter_junk_inbox
+ (CamelM365Settings *settings,
+ gboolean filter_junk_inbox);
+guint camel_m365_settings_get_timeout (CamelM365Settings *settings);
+void camel_m365_settings_set_timeout (CamelM365Settings *settings,
+ guint timeout);
+gboolean camel_m365_settings_get_override_oauth2
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_override_oauth2
+ (CamelM365Settings *settings,
+ gboolean override_oauth2);
+const gchar * camel_m365_settings_get_oauth2_tenant
+ (CamelM365Settings *settings);
+gchar * camel_m365_settings_dup_oauth2_tenant
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_oauth2_tenant
+ (CamelM365Settings *settings,
+ const gchar *tenant);
+const gchar * camel_m365_settings_get_oauth2_client_id
+ (CamelM365Settings *settings);
+gchar * camel_m365_settings_dup_oauth2_client_id
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_oauth2_client_id
+ (CamelM365Settings *settings,
+ const gchar *client_id);
+const gchar * camel_m365_settings_get_oauth2_redirect_uri
+ (CamelM365Settings *settings);
+gchar * camel_m365_settings_dup_oauth2_redirect_uri
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_oauth2_redirect_uri
+ (CamelM365Settings *settings,
+ const gchar *redirect_uri);
+guint camel_m365_settings_get_concurrent_connections
+ (CamelM365Settings *settings);
+void camel_m365_settings_set_concurrent_connections
+ (CamelM365Settings *settings,
+ guint concurrent_connections);
+
+G_END_DECLS
+
+#endif /* CAMEL_M365_SETTINGS_H */
diff --git a/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.c
b/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.c
new file mode 100644
index 00000000..60c56dfa
--- /dev/null
+++ b/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.c
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "camel-sasl-xoauth2-microsoft365.h"
+
+static CamelServiceAuthType sasl_xoauth2_microsoft365_auth_type = {
+ N_("OAuth2 (Microsoft 365)"),
+ N_("This option will use an OAuth 2.0 access token to connect to the Microsoft 365 server"),
+ "Microsoft365",
+ FALSE
+};
+
+G_DEFINE_DYNAMIC_TYPE (CamelSaslXOAuth2Microsoft365, camel_sasl_xoauth2_microsoft365,
CAMEL_TYPE_SASL_XOAUTH2)
+
+static void
+camel_sasl_xoauth2_microsoft365_class_init (CamelSaslXOAuth2Microsoft365Class *klass)
+{
+ CamelSaslClass *sasl_class;
+
+ sasl_class = CAMEL_SASL_CLASS (klass);
+ sasl_class->auth_type = &sasl_xoauth2_microsoft365_auth_type;
+}
+
+static void
+camel_sasl_xoauth2_microsoft365_class_finalize (CamelSaslXOAuth2Microsoft365Class *klass)
+{
+}
+
+static void
+camel_sasl_xoauth2_microsoft365_init (CamelSaslXOAuth2Microsoft365 *sasl)
+{
+}
+
+void
+camel_sasl_xoauth2_microsoft365_type_register (GTypeModule *type_module)
+{
+ camel_sasl_xoauth2_microsoft365_register_type (type_module);
+}
diff --git a/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.h
b/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.h
new file mode 100644
index 00000000..b6380ed5
--- /dev/null
+++ b/src/Microsoft365/common/camel-sasl-xoauth2-microsoft365.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef CAMEL_SASL_XOAUTH2_MICROSOFT365_H
+#define CAMEL_SASL_XOAUTH2_MICROSOFT365_H
+
+#include <gmodule.h>
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365 \
+ (camel_sasl_xoauth2_microsoft365_get_type ())
+#define CAMEL_SASL_XOAUTH2_MICROSOFT365(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365, CamelSaslXOAuth2Microsoft365))
+#define CAMEL_SASL_XOAUTH2_MICROSOFT365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365, CamelSaslXOAuth2Microsoft365Class))
+#define CAMEL_IS_SASL_XOAUTH2_MICROSOFT365(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365))
+#define CAMEL_IS_SASL_XOAUTH2_MICROSOFT365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365))
+#define CAMEL_SASL_XOAUTH2_MICROSOFT365_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), CAMEL_TYPE_SASL_XOAUTH2_MICROSOFT365, CamelSaslXOAuth2Microsoft365Class))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelSaslXOAuth2Microsoft365 CamelSaslXOAuth2Microsoft365;
+typedef struct _CamelSaslXOAuth2Microsoft365Class CamelSaslXOAuth2Microsoft365Class;
+typedef struct _CamelSaslXOAuth2Microsoft365Private CamelSaslXOAuth2Microsoft365Private;
+
+struct _CamelSaslXOAuth2Microsoft365 {
+ CamelSaslXOAuth2 parent;
+ CamelSaslXOAuth2Microsoft365Private *priv;
+};
+
+struct _CamelSaslXOAuth2Microsoft365Class {
+ CamelSaslXOAuth2Class parent_class;
+};
+
+GType camel_sasl_xoauth2_microsoft365_get_type(void) G_GNUC_CONST;
+
+void camel_sasl_xoauth2_microsoft365_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* CAMEL_SASL_XOAUTH2_MICROSOFT365_H */
diff --git a/src/Microsoft365/common/e-m365-connection.c b/src/Microsoft365/common/e-m365-connection.c
new file mode 100644
index 00000000..cf8e1d2d
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-connection.c
@@ -0,0 +1,6007 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <json-glib/json-glib.h>
+
+#include "camel-m365-settings.h"
+#include "e-m365-json-utils.h"
+
+#include "e-m365-connection.h"
+
+#define LOCK(x) g_rec_mutex_lock (&(x->priv->property_lock))
+#define UNLOCK(x) g_rec_mutex_unlock (&(x->priv->property_lock))
+
+#define X_EVO_M365_DATA "X-EVO-M365-DATA"
+
+typedef enum _CSMFlags {
+ CSM_DEFAULT = 0,
+ CSM_DISABLE_RESPONSE = 1 << 0
+} CSMFlags;
+
+struct _EM365ConnectionPrivate {
+ GRecMutex property_lock;
+
+ ESource *source;
+ CamelM365Settings *settings;
+ SoupSession *soup_session;
+ GProxyResolver *proxy_resolver;
+ ESoupAuthBearer *bearer_auth;
+
+ gchar *user; /* The default user for the URL */
+ gchar *impersonate_user;
+
+ gboolean ssl_info_set;
+ gchar *ssl_certificate_pem;
+ GTlsCertificateFlags ssl_certificate_errors;
+
+ gchar *hash_key; /* in the opened connections hash */
+
+ /* How many microseconds to wait, until can execute a new request.
+ This is to cover throttling and server unavailable responses.
+ https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
+ gint64 backoff_for_usec;
+};
+
+enum {
+ PROP_0,
+ PROP_PROXY_RESOLVER,
+ PROP_SETTINGS,
+ PROP_SOURCE,
+ PROP_CONCURRENT_CONNECTIONS,
+ PROP_USER, /* This one is hidden, write only */
+ PROP_USE_IMPERSONATION, /* This one is hidden, write only */
+ PROP_IMPERSONATE_USER /* This one is hidden, write only */
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (EM365Connection, e_m365_connection, G_TYPE_OBJECT)
+
+static GHashTable *opened_connections = NULL;
+G_LOCK_DEFINE_STATIC (opened_connections);
+
+static gboolean
+m365_log_enabled (void)
+{
+ static gint log_enabled = -1;
+
+ if (log_enabled == -1)
+ log_enabled = g_strcmp0 (g_getenv ("M365_DEBUG"), "1") == 0 ? 1 : 0;
+
+ return log_enabled == 1;
+}
+
+static SoupSession *
+m365_connection_ref_soup_session (EM365Connection *cnc)
+{
+ SoupSession *soup_session = NULL;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ LOCK (cnc);
+
+ if (cnc->priv->soup_session)
+ soup_session = g_object_ref (cnc->priv->soup_session);
+
+ UNLOCK (cnc);
+
+ return soup_session;
+}
+
+static void
+m365_connection_utils_ensure_bearer_auth_usage (SoupSession *session,
+ SoupMessage *message,
+ ESoupAuthBearer *bearer)
+{
+ SoupAuthManager *auth_manager;
+ SoupSessionFeature *feature;
+ SoupURI *soup_uri;
+
+ g_return_if_fail (SOUP_IS_SESSION (session));
+
+ /* Preload the SoupAuthManager with a valid "Bearer" token
+ * when using OAuth 2.0. This avoids an extra unauthorized
+ * HTTP round-trip, which apparently Google doesn't like. */
+
+ feature = soup_session_get_feature (SOUP_SESSION (session), SOUP_TYPE_AUTH_MANAGER);
+
+ if (!soup_session_feature_has_feature (feature, E_TYPE_SOUP_AUTH_BEARER)) {
+ /* Add the "Bearer" auth type to support OAuth 2.0. */
+ soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
+ }
+
+ soup_uri = message ? soup_message_get_uri (message) : NULL;
+ if (soup_uri && soup_uri->host && *soup_uri->host) {
+ soup_uri = soup_uri_copy_host (soup_uri);
+ } else {
+ soup_uri = NULL;
+ }
+
+ g_return_if_fail (soup_uri != NULL);
+
+ auth_manager = SOUP_AUTH_MANAGER (feature);
+
+ /* This will make sure the 'bearer' is used regardless of the current 'auth_manager' state.
+ See https://gitlab.gnome.org/GNOME/libsoup/-/issues/196 for more information. */
+ soup_auth_manager_clear_cached_credentials (auth_manager);
+ soup_auth_manager_use_auth (auth_manager, soup_uri, SOUP_AUTH (bearer));
+
+ soup_uri_free (soup_uri);
+}
+
+static gboolean
+m365_connection_utils_setup_bearer_auth (EM365Connection *cnc,
+ SoupSession *session,
+ SoupMessage *message,
+ gboolean is_in_authenticate_handler,
+ ESoupAuthBearer *bearer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESource *source;
+ gchar *access_token = NULL;
+ gint expires_in_seconds = -1;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), FALSE);
+
+ source = e_m365_connection_get_source (cnc);
+
+ success = e_source_get_oauth2_access_token_sync (source, cancellable,
+ &access_token, &expires_in_seconds, error);
+
+ if (success) {
+ e_soup_auth_bearer_set_access_token (bearer, access_token, expires_in_seconds);
+
+ if (!is_in_authenticate_handler) {
+ if (session)
+ g_object_ref (session);
+ else
+ session = m365_connection_ref_soup_session (cnc);
+
+ m365_connection_utils_ensure_bearer_auth_usage (session, message, bearer);
+
+ g_clear_object (&session);
+ }
+ }
+
+ g_free (access_token);
+
+ return success;
+}
+
+static gboolean
+m365_connection_utils_prepare_bearer_auth (EM365Connection *cnc,
+ SoupSession *session,
+ SoupMessage *message,
+ GCancellable *cancellable)
+{
+ ESource *source;
+ ESoupAuthBearer *using_bearer_auth;
+ gboolean success;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+
+ source = e_m365_connection_get_source (cnc);
+ if (!source)
+ return TRUE;
+
+ using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
+ if (using_bearer_auth) {
+ success = m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE,
using_bearer_auth, cancellable, &local_error);
+ g_clear_object (&using_bearer_auth);
+ } else {
+ SoupAuth *soup_auth;
+ SoupURI *soup_uri;
+
+ soup_uri = message ? soup_message_get_uri (message) : NULL;
+ if (soup_uri && soup_uri->host && *soup_uri->host) {
+ soup_uri = soup_uri_copy_host (soup_uri);
+ } else {
+ soup_uri = NULL;
+ }
+
+ g_warn_if_fail (soup_uri != NULL);
+
+ if (!soup_uri) {
+ soup_message_set_status_full (message, SOUP_STATUS_MALFORMED, "Cannot get host from
message");
+ return FALSE;
+ }
+
+ soup_auth = g_object_new (E_TYPE_SOUP_AUTH_BEARER, SOUP_AUTH_HOST, soup_uri->host, NULL);
+
+ success = m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE,
E_SOUP_AUTH_BEARER (soup_auth), cancellable, &local_error);
+ if (success)
+ e_m365_connection_set_bearer_auth (cnc, E_SOUP_AUTH_BEARER (soup_auth));
+
+ g_object_unref (soup_auth);
+ soup_uri_free (soup_uri);
+ }
+
+ if (!success) {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ soup_message_set_status (message, SOUP_STATUS_CANCELLED);
+ else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) ||
+ g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ soup_message_set_status_full (message, SOUP_STATUS_UNAUTHORIZED,
local_error->message);
+ else
+ soup_message_set_status_full (message, SOUP_STATUS_MALFORMED, local_error ?
local_error->message : _("Unknown error"));
+ }
+
+ g_clear_error (&local_error);
+
+ return success;
+}
+
+static void
+m365_connection_authenticate (SoupSession *session,
+ SoupMessage *msg,
+ SoupAuth *auth,
+ gboolean retrying,
+ gpointer user_data)
+{
+ EM365Connection *cnc = user_data;
+ ESoupAuthBearer *using_bearer_auth;
+ GError *local_error = NULL;
+
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
+
+ if (E_IS_SOUP_AUTH_BEARER (auth)) {
+ g_object_ref (auth);
+ g_warn_if_fail ((gpointer) using_bearer_auth == (gpointer) auth);
+
+ g_clear_object (&using_bearer_auth);
+ using_bearer_auth = E_SOUP_AUTH_BEARER (auth);
+
+ e_m365_connection_set_bearer_auth (cnc, using_bearer_auth);
+ }
+
+ if (!using_bearer_auth) {
+ g_warn_if_reached ();
+ return;
+ }
+
+ m365_connection_utils_setup_bearer_auth (cnc, session, msg, TRUE, E_SOUP_AUTH_BEARER (auth), NULL,
&local_error);
+
+ if (local_error)
+ soup_message_set_status_full (msg, SOUP_STATUS_IO_ERROR, local_error->message);
+
+ g_object_unref (using_bearer_auth);
+ g_clear_error (&local_error);
+}
+
+static gboolean
+m365_connection_utils_prepare_message (EM365Connection *cnc,
+ SoupSession *session,
+ SoupMessage *message,
+ GCancellable *cancellable)
+{
+ ESoupAuthBearer *using_bearer_auth;
+ ESource *source;
+ GError *local_error = NULL;
+
+ source = e_m365_connection_get_source (cnc);
+ if (source)
+ e_soup_ssl_trust_connect (message, source);
+
+ if (!m365_connection_utils_prepare_bearer_auth (cnc, session, message, cancellable))
+ return FALSE;
+
+ using_bearer_auth = e_m365_connection_ref_bearer_auth (cnc);
+
+ if (using_bearer_auth &&
+ e_soup_auth_bearer_is_expired (using_bearer_auth) &&
+ !m365_connection_utils_setup_bearer_auth (cnc, session, message, FALSE, using_bearer_auth,
cancellable, &local_error)) {
+ if (local_error) {
+ soup_message_set_status_full (message, SOUP_STATUS_BAD_REQUEST, local_error->message);
+ g_clear_error (&local_error);
+ } else {
+ soup_message_set_status (message, SOUP_STATUS_BAD_REQUEST);
+ }
+
+ g_object_unref (using_bearer_auth);
+
+ return FALSE;
+ }
+
+ g_clear_object (&using_bearer_auth);
+
+ return TRUE;
+}
+
+static void
+m365_connection_set_settings (EM365Connection *cnc,
+ CamelM365Settings *settings)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+ g_return_if_fail (CAMEL_IS_M365_SETTINGS (settings));
+ g_return_if_fail (cnc->priv->settings == NULL);
+
+ cnc->priv->settings = g_object_ref (settings);
+
+ e_binding_bind_property (
+ cnc->priv->settings, "user",
+ cnc, "user",
+ G_BINDING_DEFAULT |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ cnc->priv->settings, "use-impersonation",
+ cnc, "use-impersonation",
+ G_BINDING_DEFAULT |
+ G_BINDING_SYNC_CREATE);
+
+ /* No need to G_BINDING_SYNC_CREATE, because the 'use-impersonation' already updated the value */
+ e_binding_bind_property (
+ cnc->priv->settings, "impersonate-user",
+ cnc, "impersonate-user",
+ G_BINDING_DEFAULT);
+}
+
+static void
+m365_connection_set_source (EM365Connection *cnc,
+ ESource *source)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+ g_return_if_fail (E_IS_SOURCE (source));
+ g_return_if_fail (cnc->priv->source == NULL);
+
+ cnc->priv->source = g_object_ref (source);
+}
+
+static void
+m365_connection_take_user (EM365Connection *cnc,
+ gchar *user)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ LOCK (cnc);
+
+ if (!user || !*user)
+ g_clear_pointer (&user, g_free);
+
+ g_free (cnc->priv->user);
+ cnc->priv->user = user;
+
+ UNLOCK (cnc);
+}
+
+static void
+m365_connection_take_impersonate_user (EM365Connection *cnc,
+ gchar *impersonate_user)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ LOCK (cnc);
+
+ if (!impersonate_user || !*impersonate_user ||
+ !camel_m365_settings_get_use_impersonation (cnc->priv->settings)) {
+ g_clear_pointer (&impersonate_user, g_free);
+ }
+
+ if (g_strcmp0 (impersonate_user, cnc->priv->impersonate_user) != 0) {
+ g_free (cnc->priv->impersonate_user);
+ cnc->priv->impersonate_user = impersonate_user;
+ } else {
+ g_clear_pointer (&impersonate_user, g_free);
+ }
+
+ UNLOCK (cnc);
+}
+
+static void
+m365_connection_set_use_impersonation (EM365Connection *cnc,
+ gboolean use_impersonation)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ LOCK (cnc);
+
+ if (!use_impersonation)
+ m365_connection_take_impersonate_user (cnc, NULL);
+ else
+ m365_connection_take_impersonate_user (cnc, camel_m365_settings_dup_impersonate_user
(cnc->priv->settings));
+
+ UNLOCK (cnc);
+}
+
+static void
+m365_connection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROXY_RESOLVER:
+ e_m365_connection_set_proxy_resolver (
+ E_M365_CONNECTION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SETTINGS:
+ m365_connection_set_settings (
+ E_M365_CONNECTION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SOURCE:
+ m365_connection_set_source (
+ E_M365_CONNECTION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ e_m365_connection_set_concurrent_connections (
+ E_M365_CONNECTION (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_USER:
+ m365_connection_take_user (
+ E_M365_CONNECTION (object),
+ g_value_dup_string (value));
+ return;
+
+ case PROP_USE_IMPERSONATION:
+ m365_connection_set_use_impersonation (
+ E_M365_CONNECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_IMPERSONATE_USER:
+ m365_connection_take_impersonate_user (
+ E_M365_CONNECTION (object),
+ g_value_dup_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROXY_RESOLVER:
+ g_value_take_object (
+ value,
+ e_m365_connection_ref_proxy_resolver (
+ E_M365_CONNECTION (object)));
+ return;
+
+ case PROP_SETTINGS:
+ g_value_set_object (
+ value,
+ e_m365_connection_get_settings (
+ E_M365_CONNECTION (object)));
+ return;
+
+ case PROP_SOURCE:
+ g_value_set_object (
+ value,
+ e_m365_connection_get_source (
+ E_M365_CONNECTION (object)));
+ return;
+
+ case PROP_CONCURRENT_CONNECTIONS:
+ g_value_set_uint (
+ value,
+ e_m365_connection_get_concurrent_connections (
+ E_M365_CONNECTION (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+m365_connection_constructed (GObject *object)
+{
+ EM365Connection *cnc = E_M365_CONNECTION (object);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_connection_parent_class)->constructed (object);
+
+ if (m365_log_enabled ()) {
+ SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+
+ soup_session_add_feature (cnc->priv->soup_session, SOUP_SESSION_FEATURE (logger));
+
+ g_object_unref (logger);
+ }
+
+ soup_session_add_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_COOKIE_JAR);
+ soup_session_add_feature_by_type (cnc->priv->soup_session, E_TYPE_SOUP_AUTH_BEARER);
+ soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_BASIC);
+
+ g_signal_connect (
+ cnc->priv->soup_session, "authenticate",
+ G_CALLBACK (m365_connection_authenticate), cnc);
+
+ cnc->priv->hash_key = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (cnc->priv->settings));
+
+ if (!cnc->priv->hash_key)
+ cnc->priv->hash_key = g_strdup ("no-user");
+
+ e_binding_bind_property (
+ cnc->priv->settings, "timeout",
+ cnc->priv->soup_session, SOUP_SESSION_TIMEOUT,
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+m365_connection_dispose (GObject *object)
+{
+ EM365Connection *cnc = E_M365_CONNECTION (object);
+
+ G_LOCK (opened_connections);
+
+ /* Remove the connection from the opened connections */
+ if (opened_connections &&
+ g_hash_table_lookup (opened_connections, cnc->priv->hash_key) == (gpointer) object) {
+ g_hash_table_remove (opened_connections, cnc->priv->hash_key);
+ if (g_hash_table_size (opened_connections) == 0) {
+ g_hash_table_destroy (opened_connections);
+ opened_connections = NULL;
+ }
+ }
+
+ G_UNLOCK (opened_connections);
+
+ LOCK (cnc);
+
+ if (cnc->priv->soup_session) {
+ g_signal_handlers_disconnect_by_func (
+ cnc->priv->soup_session,
+ m365_connection_authenticate, object);
+ }
+
+ g_clear_object (&cnc->priv->source);
+ g_clear_object (&cnc->priv->settings);
+ g_clear_object (&cnc->priv->soup_session);
+ g_clear_object (&cnc->priv->proxy_resolver);
+ g_clear_object (&cnc->priv->bearer_auth);
+
+ UNLOCK (cnc);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_connection_parent_class)->dispose (object);
+}
+
+static void
+m365_connection_finalize (GObject *object)
+{
+ EM365Connection *cnc = E_M365_CONNECTION (object);
+
+ g_rec_mutex_clear (&cnc->priv->property_lock);
+ g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
+ g_clear_pointer (&cnc->priv->user, g_free);
+ g_clear_pointer (&cnc->priv->impersonate_user, g_free);
+ g_free (cnc->priv->hash_key);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_connection_parent_class)->finalize (object);
+}
+
+static void
+e_m365_connection_class_init (EM365ConnectionClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = m365_connection_set_property;
+ object_class->get_property = m365_connection_get_property;
+ object_class->constructed = m365_connection_constructed;
+ object_class->dispose = m365_connection_dispose;
+ object_class->finalize = m365_connection_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PROXY_RESOLVER,
+ g_param_spec_object (
+ "proxy-resolver",
+ "Proxy Resolver",
+ "The proxy resolver for this backend",
+ G_TYPE_PROXY_RESOLVER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SETTINGS,
+ g_param_spec_object (
+ "settings",
+ "Settings",
+ "Connection settings",
+ CAMEL_TYPE_M365_SETTINGS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE,
+ g_param_spec_object (
+ "source",
+ "Source",
+ "Corresponding ESource",
+ E_TYPE_SOURCE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONCURRENT_CONNECTIONS,
+ g_param_spec_uint (
+ "concurrent-connections",
+ "Concurrent Connections",
+ "Number of concurrent connections to use",
+ MIN_CONCURRENT_CONNECTIONS,
+ MAX_CONCURRENT_CONNECTIONS,
+ 1,
+ /* Do not construct it, otherwise it overrides the value derived from
CamelM365Settings */
+ G_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USER,
+ g_param_spec_string (
+ "user",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USE_IMPERSONATION,
+ g_param_spec_boolean (
+ "use-impersonation",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IMPERSONATE_USER,
+ g_param_spec_string (
+ "impersonate-user",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_m365_connection_init (EM365Connection *cnc)
+{
+ cnc->priv = e_m365_connection_get_instance_private (cnc);
+
+ g_rec_mutex_init (&cnc->priv->property_lock);
+
+ cnc->priv->backoff_for_usec = 0;
+ cnc->priv->soup_session = soup_session_new_with_options (
+ SOUP_SESSION_TIMEOUT, 90,
+ SOUP_SESSION_SSL_STRICT, TRUE,
+ SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+ NULL);
+
+ /* Do not use G_BINDING_SYNC_CREATE, because we don't have a GProxyResolver yet anyway. */
+ e_binding_bind_property (
+ cnc, "proxy-resolver",
+ cnc->priv->soup_session, "proxy-resolver",
+ G_BINDING_DEFAULT);
+}
+
+gboolean
+e_m365_connection_util_delta_token_failed (const GError *error)
+{
+ return g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED) ||
+ g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST);
+}
+
+EM365Connection *
+e_m365_connection_new (ESource *source,
+ CamelM365Settings *settings)
+{
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ return e_m365_connection_new_full (source, settings, TRUE);
+}
+
+EM365Connection *
+e_m365_connection_new_for_backend (EBackend *backend,
+ ESourceRegistry *registry,
+ ESource *source,
+ CamelM365Settings *settings)
+{
+ ESource *backend_source, *parent_source;
+
+ g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (CAMEL_IS_M365_SETTINGS (settings), NULL);
+
+ backend_source = e_backend_get_source (backend);
+
+ if (!backend_source)
+ return e_m365_connection_new (source, settings);
+
+ parent_source = e_source_registry_find_extension (registry, source, E_SOURCE_EXTENSION_COLLECTION);
+
+ if (parent_source) {
+ EM365Connection *cnc;
+
+ cnc = e_m365_connection_new (parent_source, settings);
+
+ g_object_unref (parent_source);
+
+ return cnc;
+ }
+
+ return e_m365_connection_new (source, settings);
+}
+
+EM365Connection *
+e_m365_connection_new_full (ESource *source,
+ CamelM365Settings *settings,
+ gboolean allow_reuse)
+{
+ EM365Connection *cnc;
+
+ if (allow_reuse) {
+ gchar *hash_key = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (settings));
+
+ if (hash_key) {
+ G_LOCK (opened_connections);
+
+ if (opened_connections) {
+ cnc = g_hash_table_lookup (opened_connections, hash_key);
+
+ if (cnc) {
+ g_object_ref (cnc);
+ G_UNLOCK (opened_connections);
+
+ g_free (hash_key);
+
+ return cnc;
+ }
+ }
+
+ G_UNLOCK (opened_connections);
+ }
+
+ g_free (hash_key);
+ }
+
+ cnc = g_object_new (E_TYPE_M365_CONNECTION,
+ "source", source,
+ "settings", settings,
+ NULL);
+
+ if (allow_reuse && cnc->priv->hash_key) {
+ G_LOCK (opened_connections);
+
+ if (!opened_connections)
+ opened_connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ g_hash_table_insert (opened_connections, g_strdup (cnc->priv->hash_key), cnc);
+
+ G_UNLOCK (opened_connections);
+ }
+
+ return cnc;
+}
+
+ESource *
+e_m365_connection_get_source (EM365Connection *cnc)
+{
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ return cnc->priv->source;
+}
+
+CamelM365Settings *
+e_m365_connection_get_settings (EM365Connection *cnc)
+{
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ return cnc->priv->settings;
+}
+
+guint
+e_m365_connection_get_concurrent_connections (EM365Connection *cnc)
+{
+ guint current_cc = 0;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), 1);
+
+ LOCK (cnc);
+
+ g_object_get (G_OBJECT (cnc->priv->soup_session), SOUP_SESSION_MAX_CONNS, ¤t_cc, NULL);
+
+ UNLOCK (cnc);
+
+ return current_cc;
+}
+
+void
+e_m365_connection_set_concurrent_connections (EM365Connection *cnc,
+ guint concurrent_connections)
+{
+ guint current_cc;
+
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ concurrent_connections = CLAMP (concurrent_connections, MIN_CONCURRENT_CONNECTIONS,
MAX_CONCURRENT_CONNECTIONS);
+
+ current_cc = e_m365_connection_get_concurrent_connections (cnc);
+
+ if (current_cc == concurrent_connections)
+ return;
+
+ LOCK (cnc);
+
+ g_object_set (G_OBJECT (cnc->priv->soup_session),
+ SOUP_SESSION_MAX_CONNS, concurrent_connections,
+ SOUP_SESSION_MAX_CONNS_PER_HOST, concurrent_connections,
+ NULL);
+
+ UNLOCK (cnc);
+
+ g_object_notify (G_OBJECT (cnc), "concurrent-connections");
+}
+
+GProxyResolver *
+e_m365_connection_ref_proxy_resolver (EM365Connection *cnc)
+{
+ GProxyResolver *proxy_resolver = NULL;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ LOCK (cnc);
+
+ if (cnc->priv->proxy_resolver)
+ proxy_resolver = g_object_ref (cnc->priv->proxy_resolver);
+
+ UNLOCK (cnc);
+
+ return proxy_resolver;
+}
+
+void
+e_m365_connection_set_proxy_resolver (EM365Connection *cnc,
+ GProxyResolver *proxy_resolver)
+{
+ gboolean notify = FALSE;
+
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ LOCK (cnc);
+
+ /* Emitting a "notify" signal unnecessarily might have
+ * unwanted side effects like cancelling a SoupMessage.
+ * Only emit if we now have a different GProxyResolver. */
+
+ if (proxy_resolver != cnc->priv->proxy_resolver) {
+ g_clear_object (&cnc->priv->proxy_resolver);
+ cnc->priv->proxy_resolver = proxy_resolver;
+
+ if (proxy_resolver)
+ g_object_ref (proxy_resolver);
+
+ notify = TRUE;
+ }
+
+ UNLOCK (cnc);
+
+ if (notify)
+ g_object_notify (G_OBJECT (cnc), "proxy-resolver");
+}
+
+ESoupAuthBearer *
+e_m365_connection_ref_bearer_auth (EM365Connection *cnc)
+{
+ ESoupAuthBearer *res = NULL;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ LOCK (cnc);
+
+ if (cnc->priv->bearer_auth)
+ res = g_object_ref (cnc->priv->bearer_auth);
+
+ UNLOCK (cnc);
+
+ return res;
+}
+
+void
+e_m365_connection_set_bearer_auth (EM365Connection *cnc,
+ ESoupAuthBearer *bearer_auth)
+{
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ LOCK (cnc);
+
+ if (cnc->priv->bearer_auth != bearer_auth) {
+ g_clear_object (&cnc->priv->bearer_auth);
+
+ cnc->priv->bearer_auth = bearer_auth;
+
+ if (cnc->priv->bearer_auth)
+ g_object_ref (cnc->priv->bearer_auth);
+ }
+
+ UNLOCK (cnc);
+}
+
+static void
+m365_connection_request_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ EFlag *flag = user_data;
+
+ g_return_if_fail (flag != NULL);
+
+ e_flag_set (flag);
+}
+
+static void
+m365_connection_extract_ssl_data (EM365Connection *cnc,
+ SoupMessage *message)
+{
+ GTlsCertificate *certificate = NULL;
+
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+ LOCK (cnc);
+
+ g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
+ cnc->priv->ssl_info_set = FALSE;
+
+ g_object_get (G_OBJECT (message),
+ "tls-certificate", &certificate,
+ "tls-errors", &cnc->priv->ssl_certificate_errors,
+ NULL);
+
+ if (certificate) {
+ g_object_get (certificate, "certificate-pem", &cnc->priv->ssl_certificate_pem, NULL);
+ cnc->priv->ssl_info_set = TRUE;
+
+ g_object_unref (certificate);
+ }
+
+ UNLOCK (cnc);
+}
+
+/* An example error response:
+
+ {
+ "error": {
+ "code": "BadRequest",
+ "message": "Parsing Select and Expand failed.",
+ "innerError": {
+ "request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "date": "2020-06-10T13:44:43"
+ }
+ }
+ }
+
+ */
+static gboolean
+m365_connection_extract_error (JsonNode *node,
+ guint status_code,
+ GError **error)
+{
+ JsonObject *object;
+ const gchar *code, *message;
+
+ if (!node || !JSON_NODE_HOLDS_OBJECT (node))
+ return FALSE;
+
+ object = e_m365_json_get_object_member (json_node_get_object (node), "error");
+
+ if (!object)
+ return FALSE;
+
+ code = e_m365_json_get_string_member (object, "code", NULL);
+ message = e_m365_json_get_string_member (object, "message", NULL);
+
+ if (!code && !message)
+ return FALSE;
+
+ if (!status_code || SOUP_STATUS_IS_SUCCESSFUL (status_code))
+ status_code = SOUP_STATUS_MALFORMED;
+ else if (g_strcmp0 (code, "ErrorInvalidUser") == 0)
+ status_code = SOUP_STATUS_UNAUTHORIZED;
+
+ if (code && message)
+ g_set_error (error, SOUP_HTTP_ERROR, status_code, "%s: %s", code, message);
+ else
+ g_set_error_literal (error, SOUP_HTTP_ERROR, status_code, code ? code : message);
+
+ return TRUE;
+}
+
+typedef gboolean (* EM365ResponseFunc) (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode *node,
+ gpointer user_data,
+ gchar **out_next_link,
+ GCancellable *cancellable,
+ GError **error);
+
+/* (transfer full) (nullable): Free the *out_node with json_node_unref(), if not NULL;
+ It can return 'success', even when the *out_node is NULL. */
+gboolean
+e_m365_connection_json_node_from_message (SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode **out_node,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonObject *message_json_object;
+ gboolean success = TRUE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+ g_return_val_if_fail (out_node != NULL, FALSE);
+
+ *out_node = NULL;
+
+ message_json_object = g_object_get_data (G_OBJECT (message), X_EVO_M365_DATA);
+
+ if (message_json_object) {
+ *out_node = json_node_init_object (json_node_new (JSON_NODE_OBJECT), message_json_object);
+
+ success = !m365_connection_extract_error (*out_node, message->status_code, &local_error);
+ } else {
+ const gchar *content_type;
+
+ content_type = message->response_headers ? soup_message_headers_get_content_type
(message->response_headers, NULL) : NULL;
+
+ if (content_type && g_ascii_strcasecmp (content_type, "application/json") == 0) {
+ JsonParser *json_parser;
+
+ json_parser = json_parser_new_immutable ();
+
+ if (input_stream) {
+ success = json_parser_load_from_stream (json_parser, input_stream,
cancellable, error);
+ } else {
+ SoupBuffer *sbuffer;
+
+ sbuffer = soup_message_body_flatten (message->response_body);
+
+ if (sbuffer) {
+ success = json_parser_load_from_data (json_parser, sbuffer->data,
sbuffer->length, error);
+ soup_buffer_free (sbuffer);
+ } else {
+ /* This should not happen, it's for safety check only, thus the
string is not localized */
+ success = FALSE;
+ g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, "No
JSON data found");
+ }
+ }
+
+ if (success) {
+ *out_node = json_parser_steal_root (json_parser);
+
+ success = !m365_connection_extract_error (*out_node, message->status_code,
&local_error);
+ }
+
+ g_object_unref (json_parser);
+ }
+ }
+
+ if (!success && *out_node) {
+ json_node_unref (*out_node);
+ *out_node = NULL;
+ }
+
+ if (local_error)
+ g_propagate_error (error, local_error);
+
+ return success;
+}
+
+static gboolean
+m365_connection_send_request_sync (EM365Connection *cnc,
+ SoupMessage *message,
+ EM365ResponseFunc response_func,
+ EM365ConnectionRawDataFunc raw_data_func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupSession *soup_session;
+ gint need_retry_seconds = 5;
+ gboolean success = FALSE, need_retry = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+ g_return_val_if_fail (response_func != NULL || raw_data_func != NULL, FALSE);
+ g_return_val_if_fail (response_func == NULL || raw_data_func == NULL, FALSE);
+
+ while (need_retry && !g_cancellable_is_cancelled (cancellable) && message->status_code !=
SOUP_STATUS_CANCELLED) {
+ need_retry = FALSE;
+
+ LOCK (cnc);
+
+ if (cnc->priv->backoff_for_usec) {
+ EFlag *flag;
+ gint64 wait_ms;
+ gulong handler_id = 0;
+
+ wait_ms = cnc->priv->backoff_for_usec / G_TIME_SPAN_MILLISECOND;
+
+ UNLOCK (cnc);
+
+ flag = e_flag_new ();
+
+ if (cancellable) {
+ handler_id = g_cancellable_connect (cancellable, G_CALLBACK
(m365_connection_request_cancelled_cb),
+ flag, NULL);
+ }
+
+ while (wait_ms > 0 && !g_cancellable_is_cancelled (cancellable) &&
message->status_code != SOUP_STATUS_CANCELLED) {
+ gint64 now = g_get_monotonic_time ();
+ gint left_minutes, left_seconds;
+
+ left_minutes = wait_ms / 60000;
+ left_seconds = (wait_ms / 1000) % 60;
+
+ if (left_minutes > 0) {
+ camel_operation_push_message (cancellable,
+ g_dngettext (GETTEXT_PACKAGE,
+ "Microsoft 365 server is busy, waiting to retry
(%d:%02d minute)",
+ "Microsoft 365 server is busy, waiting to retry
(%d:%02d minutes)", left_minutes),
+ left_minutes, left_seconds);
+ } else {
+ camel_operation_push_message (cancellable,
+ g_dngettext (GETTEXT_PACKAGE,
+ "Microsoft 365 server is busy, waiting to retry (%d
second)",
+ "Microsoft 365 server is busy, waiting to retry (%d
seconds)", left_seconds),
+ left_seconds);
+ }
+
+ e_flag_wait_until (flag, now + (G_TIME_SPAN_MILLISECOND * (wait_ms > 1000 ?
1000 : wait_ms)));
+ e_flag_clear (flag);
+
+ now = g_get_monotonic_time () - now;
+ now = now / G_TIME_SPAN_MILLISECOND;
+
+ if (now >= wait_ms)
+ wait_ms = 0;
+ wait_ms -= now;
+
+ camel_operation_pop_message (cancellable);
+ }
+
+ if (handler_id)
+ g_cancellable_disconnect (cancellable, handler_id);
+
+ e_flag_free (flag);
+
+ LOCK (cnc);
+
+ cnc->priv->backoff_for_usec = 0;
+ }
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ UNLOCK (cnc);
+
+ soup_message_set_status (message, SOUP_STATUS_CANCELLED);
+
+ return FALSE;
+ }
+
+ soup_session = cnc->priv->soup_session ? g_object_ref (cnc->priv->soup_session) : NULL;
+
+ g_clear_pointer (&cnc->priv->ssl_certificate_pem, g_free);
+ cnc->priv->ssl_certificate_errors = 0;
+ cnc->priv->ssl_info_set = FALSE;
+
+ UNLOCK (cnc);
+
+ if (soup_session &&
+ m365_connection_utils_prepare_message (cnc, soup_session, message, cancellable)) {
+ GInputStream *input_stream;
+
+ input_stream = soup_session_send (soup_session, message, cancellable, error);
+
+ success = input_stream != NULL;
+
+ if (success && m365_log_enabled ())
+ input_stream = e_soup_logger_attach (message, input_stream);
+
+ /* Throttling - https://docs.microsoft.com/en-us/graph/throttling */
+ if (message->status_code == 429 ||
+ /*
https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
+ message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE) {
+ need_retry = TRUE;
+ } else if (message->status_code == SOUP_STATUS_SSL_FAILED) {
+ m365_connection_extract_ssl_data (cnc, message);
+ }
+
+ if (need_retry) {
+ const gchar *retry_after_str;
+ gint64 retry_after;
+
+ retry_after_str = message->response_headers ? soup_message_headers_get_one
(message->response_headers, "Retry-After") : NULL;
+
+ if (retry_after_str && *retry_after_str)
+ retry_after = g_ascii_strtoll (retry_after_str, NULL, 10);
+ else
+ retry_after = 0;
+
+ if (retry_after > 0)
+ need_retry_seconds = retry_after;
+ else if (need_retry_seconds < 120)
+ need_retry_seconds *= 2;
+
+ LOCK (cnc);
+
+ if (cnc->priv->backoff_for_usec < need_retry_seconds * G_USEC_PER_SEC)
+ cnc->priv->backoff_for_usec = need_retry_seconds * G_USEC_PER_SEC;
+
+ if (message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE)
+ soup_session_abort (soup_session);
+
+ UNLOCK (cnc);
+
+ success = FALSE;
+ } else if (success && raw_data_func && SOUP_STATUS_IS_SUCCESSFUL
(message->status_code)) {
+ success = raw_data_func (cnc, message, input_stream, func_user_data,
cancellable, error);
+ } else if (success) {
+ JsonNode *node = NULL;
+
+ success = e_m365_connection_json_node_from_message (message, input_stream,
&node, cancellable, error);
+
+ if (success) {
+ gchar *next_link = NULL;
+
+ success = response_func && response_func (cnc, message, input_stream,
node, func_user_data, &next_link, cancellable, error);
+
+ if (success && next_link && *next_link) {
+ SoupURI *suri;
+
+ suri = soup_uri_new (next_link);
+
+ /* Check whether the server returned correct nextLink URI */
+ g_warn_if_fail (suri != NULL);
+
+ if (suri) {
+ need_retry = TRUE;
+
+ soup_message_set_uri (message, suri);
+ soup_uri_free (suri);
+ }
+ }
+
+ g_free (next_link);
+ } else if (error && !*error && message->status_code &&
!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
+ if (message->status_code == SOUP_STATUS_CANCELLED) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ message->reason_phrase ? message->reason_phrase :
soup_status_get_phrase (message->status_code));
+ } else {
+ g_set_error_literal (error, SOUP_HTTP_ERROR,
message->status_code,
+ message->reason_phrase ? message->reason_phrase :
soup_status_get_phrase (message->status_code));
+ }
+ }
+
+ if (node)
+ json_node_unref (node);
+ }
+
+ g_clear_object (&input_stream);
+ } else {
+ if (!message->status_code)
+ soup_message_set_status (message, SOUP_STATUS_CANCELLED);
+
+ if (message->status_code == SOUP_STATUS_CANCELLED) {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ message->reason_phrase ? message->reason_phrase :
soup_status_get_phrase (message->status_code));
+ } else {
+ g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code,
+ message->reason_phrase ? message->reason_phrase :
soup_status_get_phrase (message->status_code));
+ }
+ }
+
+ g_clear_object (&soup_session);
+
+ if (need_retry) {
+ success = FALSE;
+ g_clear_error (error);
+ }
+ }
+
+ return success;
+}
+
+static gboolean
+e_m365_read_no_response_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *raw_data_stream,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* This is used when no response is expected from the server.
+ Read the data stream only if debugging is on, in case
+ the server returns anything interesting. */
+
+ if (m365_log_enabled ()) {
+ gchar buffer[4096];
+
+ while (g_input_stream_read (raw_data_stream, buffer, sizeof (buffer), cancellable, error) >
0) {
+ /* Do nothing, just read it, thus it's shown in the debug output */
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+e_m365_read_to_byte_array_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *raw_data_stream,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GByteArray **out_byte_array = user_data;
+ gchar buffer[4096];
+ gssize n_read;
+
+ g_return_val_if_fail (message != NULL, FALSE);
+ g_return_val_if_fail (out_byte_array != NULL, FALSE);
+
+ if (!*out_byte_array) {
+ goffset content_length;
+
+ content_length = soup_message_headers_get_content_length (message->response_headers);
+
+ if (!content_length || content_length > 65536)
+ content_length = 65535;
+
+ *out_byte_array = g_byte_array_sized_new (content_length);
+ }
+
+ while (n_read = g_input_stream_read (raw_data_stream, buffer, sizeof (buffer), cancellable, error),
n_read > 0) {
+ g_byte_array_append (*out_byte_array, (const guint8 *) buffer, n_read);
+ }
+
+ return !n_read;
+}
+
+typedef struct _EM365ResponseData {
+ EM365ConnectionJsonFunc json_func;
+ gpointer func_user_data;
+ gboolean read_only_once; /* To be able to just try authentication */
+ GSList **out_items; /* JsonObject * */
+ gchar **out_delta_link; /* set only if available and not NULL */
+ GPtrArray *inout_requests; /* SoupMessage *, for the batch request */
+} EM365ResponseData;
+
+static gboolean
+e_m365_read_valued_response_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode *node,
+ gpointer user_data,
+ gchar **out_next_link,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData *response_data = user_data;
+ JsonObject *object;
+ JsonArray *value;
+ const gchar *delta_link;
+ GSList *items = NULL;
+ gboolean can_continue = TRUE;
+ guint ii, len;
+
+ g_return_val_if_fail (response_data != NULL, FALSE);
+ g_return_val_if_fail (out_next_link != NULL, FALSE);
+ g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
+
+ object = json_node_get_object (node);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ if (!response_data->read_only_once)
+ *out_next_link = g_strdup (e_m365_json_get_string_member (object, "@odata.nextLink", NULL));
+
+ delta_link = e_m365_json_get_string_member (object, "@odata.deltaLink", NULL);
+
+ if (delta_link && response_data->out_delta_link)
+ *response_data->out_delta_link = g_strdup (delta_link);
+
+ value = e_m365_json_get_array_member (object, "value");
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ len = json_array_get_length (value);
+
+ for (ii = 0; ii < len; ii++) {
+ JsonNode *elem = json_array_get_element (value, ii);
+
+ g_warn_if_fail (JSON_NODE_HOLDS_OBJECT (elem));
+
+ if (JSON_NODE_HOLDS_OBJECT (elem)) {
+ JsonObject *elem_object = json_node_get_object (elem);
+
+ if (elem_object) {
+ if (response_data->out_items)
+ *response_data->out_items = g_slist_prepend
(*response_data->out_items, json_object_ref (elem_object));
+ else
+ items = g_slist_prepend (items, json_object_ref (elem_object));
+ }
+ }
+ }
+
+ if (response_data->json_func)
+ can_continue = response_data->json_func (cnc, items, response_data->func_user_data,
cancellable, error);
+
+ g_slist_free_full (items, (GDestroyNotify) json_object_unref);
+
+ return can_continue;
+}
+
+static gboolean
+e_m365_read_json_object_response_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode *node,
+ gpointer user_data,
+ gchar **out_next_link,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonObject **out_json_object = user_data;
+ JsonObject *object;
+
+ g_return_val_if_fail (out_json_object != NULL, FALSE);
+ g_return_val_if_fail (out_next_link != NULL, FALSE);
+ g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
+
+ object = json_node_get_object (node);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ *out_json_object = json_object_ref (object);
+
+ return TRUE;
+}
+
+static SoupMessage *
+m365_connection_new_soup_message (const gchar *method,
+ const gchar *uri,
+ CSMFlags csm_flags,
+ GError **error)
+{
+ SoupMessage *message;
+
+ g_return_val_if_fail (method != NULL, NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ message = soup_message_new (method, uri);
+
+ if (message) {
+ soup_message_headers_append (message->request_headers, "Connection", "Close");
+ soup_message_headers_append (message->request_headers, "User-Agent", "Evolution-M365/"
VERSION);
+
+ /* Disable caching for proxies (RFC 4918, section 10.4.5) */
+ soup_message_headers_append (message->request_headers, "Cache-Control", "no-cache");
+ soup_message_headers_append (message->request_headers, "Pragma", "no-cache");
+
+ if ((csm_flags & CSM_DISABLE_RESPONSE) != 0)
+ soup_message_headers_append (message->request_headers, "Prefer", "return=minimal");
+ } else {
+ g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Malformed URI: ā%sā"), uri);
+ }
+
+ return message;
+}
+
+gboolean
+e_m365_connection_get_ssl_error_details (EM365Connection *cnc,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors)
+{
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_certificate_pem != NULL, FALSE);
+ g_return_val_if_fail (out_certificate_errors != NULL, FALSE);
+
+ LOCK (cnc);
+
+ if (!cnc->priv->ssl_info_set) {
+ UNLOCK (cnc);
+ return FALSE;
+ }
+
+ *out_certificate_pem = g_strdup (cnc->priv->ssl_certificate_pem);
+ *out_certificate_errors = cnc->priv->ssl_certificate_errors;
+
+ UNLOCK (cnc);
+
+ return TRUE;
+}
+
+ESourceAuthenticationResult
+e_m365_connection_authenticate_sync (EM365Connection *cnc,
+ const gchar *user_override,
+ EM365FolderKind kind,
+ const gchar *group_id,
+ const gchar *folder_id,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESourceAuthenticationResult result = E_SOURCE_AUTHENTICATION_ERROR;
+ JsonObject *object = NULL;
+ gboolean success = FALSE;
+ GError *local_error = NULL;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), result);
+
+ switch (kind) {
+ default:
+ g_warn_if_reached ();
+ /* Falls through */
+ case E_M365_FOLDER_KIND_UNKNOWN:
+ case E_M365_FOLDER_KIND_MAIL:
+ if (!folder_id || !*folder_id)
+ folder_id = "inbox";
+
+ success = e_m365_connection_get_mail_folder_sync (cnc, user_override, folder_id,
"displayName", &object, cancellable, &local_error);
+ break;
+ case E_M365_FOLDER_KIND_CONTACTS:
+ if (!folder_id || !*folder_id)
+ folder_id = "contacts";
+
+ success = e_m365_connection_get_contacts_folder_sync (cnc, user_override, folder_id,
"displayName", &object, cancellable, &local_error);
+ break;
+ case E_M365_FOLDER_KIND_CALENDAR:
+ if (folder_id && !*folder_id)
+ folder_id = NULL;
+
+ success = e_m365_connection_get_calendar_folder_sync (cnc, user_override, group_id,
folder_id, "name", &object, cancellable, error);
+ break;
+ case E_M365_FOLDER_KIND_TASKS:
+ if (!folder_id || !*folder_id)
+ folder_id = "tasks";
+
+ success = e_m365_connection_get_task_folder_sync (cnc, user_override, group_id, folder_id,
"name", &object, cancellable, error);
+ break;
+ }
+
+ if (success) {
+ result = E_SOURCE_AUTHENTICATION_ACCEPTED;
+ } else {
+ if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
+ local_error->domain = G_IO_ERROR;
+ local_error->code = G_IO_ERROR_CANCELLED;
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED)) {
+ result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
+
+ if (out_certificate_pem || out_certificate_errors)
+ e_m365_connection_get_ssl_error_details (cnc, out_certificate_pem,
out_certificate_errors);
+ } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+ ESoupAuthBearer *bearer;
+
+ bearer = e_m365_connection_ref_bearer_auth (cnc);
+
+ if (bearer) {
+ LOCK (cnc);
+
+ if (cnc->priv->impersonate_user) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ } else {
+ result = E_SOURCE_AUTHENTICATION_REJECTED;
+ }
+
+ UNLOCK (cnc);
+ } else {
+ result = E_SOURCE_AUTHENTICATION_REQUIRED;
+ }
+
+ g_clear_object (&bearer);
+ g_clear_error (&local_error);
+ }
+
+ if (local_error) {
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ }
+ }
+
+ if (object)
+ json_object_unref (object);
+
+ g_clear_error (&local_error);
+
+ return result;
+}
+
+gboolean
+e_m365_connection_disconnect_sync (EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+
+ LOCK (cnc);
+
+ soup_session_abort (cnc->priv->soup_session);
+
+ UNLOCK (cnc);
+
+ return TRUE;
+}
+
+/* Expects NULL-terminated pair of parameters 'name', 'value'; if 'value' is NULL, the parameter is skipped.
+ An empty 'name' can add the 'value' into the path. These can be only before query parameters. */
+gchar *
+e_m365_connection_construct_uri (EM365Connection *cnc,
+ gboolean include_user,
+ const gchar *user_override,
+ EM365ApiVersion api_version,
+ const gchar *api_part,
+ const gchar *resource,
+ const gchar *id,
+ const gchar *path,
+ ...)
+{
+ va_list args;
+ const gchar *name, *value;
+ gboolean first_param = TRUE;
+ GString *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+
+ if (!api_part)
+ api_part = "users";
+
+ uri = g_string_sized_new (128);
+
+ /* https://graph.microsoft.com/v1.0/users/XUSERX/mailFolders */
+
+ g_string_append (uri, "https://graph.microsoft.com");
+
+ switch (api_version) {
+ case E_M365_API_V1_0:
+ g_string_append_c (uri, '/');
+ g_string_append (uri, "v1.0");
+ break;
+ case E_M365_API_BETA:
+ g_string_append_c (uri, '/');
+ g_string_append (uri, "beta");
+ break;
+ }
+
+ if (*api_part) {
+ g_string_append_c (uri, '/');
+ g_string_append (uri, api_part);
+ }
+
+ if (include_user) {
+ const gchar *use_user;
+
+ LOCK (cnc);
+
+ if (user_override)
+ use_user = user_override;
+ else if (cnc->priv->impersonate_user)
+ use_user = cnc->priv->impersonate_user;
+ else
+ use_user = cnc->priv->user;
+
+ if (use_user) {
+ gchar *encoded;
+
+ encoded = soup_uri_encode (use_user, NULL);
+
+ g_string_append_c (uri, '/');
+ g_string_append (uri, encoded);
+
+ g_free (encoded);
+ }
+
+ UNLOCK (cnc);
+ }
+
+ if (resource && *resource) {
+ g_string_append_c (uri, '/');
+ g_string_append (uri, resource);
+ }
+
+ if (id && *id) {
+ g_string_append_c (uri, '/');
+ g_string_append (uri, id);
+ }
+
+ if (path && *path) {
+ g_string_append_c (uri, '/');
+ g_string_append (uri, path);
+ }
+
+ va_start (args, path);
+
+ name = va_arg (args, const gchar *);
+
+ while (name) {
+ value = va_arg (args, const gchar *);
+
+ if (*name && value) {
+ g_string_append_c (uri, first_param ? '?' : '&');
+
+ first_param = FALSE;
+
+ g_string_append (uri, name);
+ g_string_append_c (uri, '=');
+
+ if (*value) {
+ gchar *encoded;
+
+ encoded = soup_uri_encode (value, NULL);
+
+ g_string_append (uri, encoded);
+
+ g_free (encoded);
+ }
+ } else if (!*name && value && *value) {
+ /* Warn when adding path after additional query parameters */
+ g_warn_if_fail (first_param);
+
+ g_string_append_c (uri, '/');
+ g_string_append (uri, value);
+ }
+
+ name = va_arg (args, const gchar *);
+ }
+
+ va_end (args);
+
+ return g_string_free (uri, FALSE);
+}
+
+static void
+e_m365_connection_set_json_body (SoupMessage *message,
+ JsonBuilder *builder)
+{
+ JsonGenerator *generator;
+ JsonNode *node;
+ gchar *data;
+ gsize data_length = 0;
+
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+ g_return_if_fail (builder != NULL);
+
+ node = json_builder_get_root (builder);
+
+ generator = json_generator_new ();
+ json_generator_set_root (generator, node);
+
+ data = json_generator_to_data (generator, &data_length);
+
+ soup_message_headers_set_content_type (message->request_headers, "application/json", NULL);
+
+ if (data)
+ soup_message_body_append_take (message->request_body, (guchar *) data, data_length);
+
+ g_object_unref (generator);
+ json_node_unref (node);
+}
+
+static void
+e_m365_fill_message_headers_cb (JsonObject *object,
+ const gchar *member_name,
+ JsonNode *member_node,
+ gpointer user_data)
+{
+ SoupMessage *message = user_data;
+
+ g_return_if_fail (message != NULL);
+ g_return_if_fail (member_name != NULL);
+ g_return_if_fail (member_node != NULL);
+
+ if (JSON_NODE_HOLDS_VALUE (member_node)) {
+ const gchar *value;
+
+ value = json_node_get_string (member_node);
+
+ if (value)
+ soup_message_headers_replace (message->response_headers, member_name, value);
+ }
+}
+
+static void
+e_m365_connection_fill_batch_response (SoupMessage *message,
+ JsonObject *object)
+{
+ JsonObject *subobject;
+
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+ g_return_if_fail (object != NULL);
+
+ message->status_code = e_m365_json_get_int_member (object, "status", SOUP_STATUS_MALFORMED);
+
+ subobject = e_m365_json_get_object_member (object, "headers");
+
+ if (subobject)
+ json_object_foreach_member (subobject, e_m365_fill_message_headers_cb, message);
+
+ subobject = e_m365_json_get_object_member (object, "body");
+
+ if (subobject)
+ g_object_set_data_full (G_OBJECT (message), X_EVO_M365_DATA, json_object_ref (subobject),
(GDestroyNotify) json_object_unref);
+}
+
+static gboolean
+e_m365_read_batch_response_cb (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode *node,
+ gpointer user_data,
+ gchar **out_next_link,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *requests = user_data;
+ JsonObject *object;
+ JsonArray *responses;
+ guint ii, len;
+
+ g_return_val_if_fail (requests != NULL, FALSE);
+ g_return_val_if_fail (out_next_link != NULL, FALSE);
+ g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), FALSE);
+
+ object = json_node_get_object (node);
+ g_return_val_if_fail (object != NULL, FALSE);
+
+ *out_next_link = g_strdup (e_m365_json_get_string_member (object, "@odata.nextLink", NULL));
+
+ responses = e_m365_json_get_array_member (object, "responses");
+ g_return_val_if_fail (responses != NULL, FALSE);
+
+ len = json_array_get_length (responses);
+
+ for (ii = 0; ii < len; ii++) {
+ JsonNode *elem = json_array_get_element (responses, ii);
+
+ g_warn_if_fail (JSON_NODE_HOLDS_OBJECT (elem));
+
+ if (JSON_NODE_HOLDS_OBJECT (elem)) {
+ JsonObject *elem_object = json_node_get_object (elem);
+
+ if (elem_object) {
+ const gchar *id_str;
+
+ id_str = e_m365_json_get_string_member (elem_object, "id", NULL);
+
+ if (id_str) {
+ guint id;
+
+ id = (guint) g_ascii_strtoull (id_str, NULL, 10);
+
+ if (id < requests->len)
+ e_m365_connection_fill_batch_response (g_ptr_array_index
(requests, id), elem_object);
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* https://docs.microsoft.com/en-us/graph/json-batching */
+
+static gboolean
+e_m365_connection_batch_request_internal_sync (EM365Connection *cnc,
+ EM365ApiVersion api_version,
+ GPtrArray *requests, /* SoupMessage * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success = TRUE;
+ gchar *uri, buff[128];
+ guint ii;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (requests != NULL, FALSE);
+ g_return_val_if_fail (requests->len > 0, FALSE);
+ g_return_val_if_fail (requests->len <= E_M365_BATCH_MAX_REQUESTS, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, FALSE, NULL, api_version, "",
+ "$batch", NULL, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_begin_array_member (builder, "requests");
+
+ for (ii = 0; success && ii < requests->len; ii++) {
+ SoupMessageHeadersIter iter;
+ SoupMessage *submessage;
+ SoupURI *suri;
+ gboolean has_headers = FALSE;
+ const gchar *hdr_name, *hdr_value, *use_uri;
+ gboolean is_application_json = FALSE;
+
+ submessage = g_ptr_array_index (requests, ii);
+
+ if (!submessage)
+ continue;
+
+ submessage->status_code = SOUP_STATUS_IO_ERROR;
+
+ suri = soup_message_get_uri (submessage);
+ uri = suri ? soup_uri_to_string (suri, TRUE) : NULL;
+
+ if (!uri) {
+ submessage->status_code = SOUP_STATUS_MALFORMED;
+ continue;
+ }
+
+ use_uri = uri;
+
+ /* The 'url' is without the API part, it is derived from the main request */
+ if (g_str_has_prefix (use_uri, "/v1.0/") ||
+ g_str_has_prefix (use_uri, "/beta/"))
+ use_uri += 5;
+
+ g_snprintf (buff, sizeof (buff), "%d", ii);
+
+ e_m365_json_begin_object_member (builder, NULL);
+
+ e_m365_json_add_string_member (builder, "id", buff);
+ e_m365_json_add_string_member (builder, "method", submessage->method);
+ e_m365_json_add_string_member (builder, "url", use_uri);
+
+ g_free (uri);
+
+ soup_message_headers_iter_init (&iter, submessage->request_headers);
+
+ while (soup_message_headers_iter_next (&iter, &hdr_name, &hdr_value)) {
+ if (hdr_name && *hdr_name && hdr_value &&
+ !camel_strcase_equal (hdr_name, "Connection") &&
+ !camel_strcase_equal (hdr_name, "User-Agent")) {
+ if (camel_strcase_equal (hdr_name, "Content-Type") &&
+ camel_strcase_equal (hdr_value, "application/json"))
+ is_application_json = TRUE;
+
+ if (!has_headers) {
+ has_headers = TRUE;
+
+ e_m365_json_begin_object_member (builder, "headers");
+ }
+
+ e_m365_json_add_string_member (builder, hdr_name, hdr_value);
+ }
+ }
+
+ if (has_headers)
+ e_m365_json_end_object_member (builder); /* headers */
+
+ if (submessage->request_body) {
+ SoupBuffer *sbuffer;
+
+ sbuffer = soup_message_body_flatten (submessage->request_body);
+
+ if (sbuffer && sbuffer->length > 0) {
+ if (is_application_json) {
+ /* The server needs it unpacked, not as a plain string */
+ JsonParser *parser;
+ JsonNode *node;
+
+ parser = json_parser_new_immutable ();
+
+ success = json_parser_load_from_data (parser, sbuffer->data,
sbuffer->length, error);
+
+ if (!success)
+ g_prefix_error (error, "%s", _("Failed to parse own Json
data"));
+
+ node = success ? json_parser_steal_root (parser) : NULL;
+
+ if (node) {
+ json_builder_set_member_name (builder, "body");
+ json_builder_add_value (builder, node);
+ }
+
+ g_clear_object (&parser);
+ } else {
+ e_m365_json_add_string_member (builder, "body", sbuffer->data);
+ }
+ }
+
+ if (sbuffer)
+ soup_buffer_free (sbuffer);
+ }
+
+ e_m365_json_end_object_member (builder); /* unnamed object */
+ }
+
+ e_m365_json_end_array_member (builder);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ soup_message_headers_append (message->request_headers, "Accept", "application/json");
+
+ g_object_unref (builder);
+
+ success = success && m365_connection_send_request_sync (cnc, message, e_m365_read_batch_response_cb,
NULL, requests, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* The 'requests' contains a SoupMessage * objects, from which are read
+ the request data and on success the SoupMessage's 'response' properties
+ are filled accordingly.
+ */
+gboolean
+e_m365_connection_batch_request_sync (EM365Connection *cnc,
+ EM365ApiVersion api_version,
+ GPtrArray *requests, /* SoupMessage * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *use_requests;
+ gint need_retry_seconds = 5;
+ gboolean success, need_retry = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (requests != NULL, FALSE);
+ g_return_val_if_fail (requests->len > 0, FALSE);
+ g_return_val_if_fail (requests->len <= E_M365_BATCH_MAX_REQUESTS, FALSE);
+
+ use_requests = requests;
+
+ while (need_retry) {
+ need_retry = FALSE;
+
+ success = e_m365_connection_batch_request_internal_sync (cnc, api_version, use_requests,
cancellable, error);
+
+ if (success) {
+ GPtrArray *new_requests = NULL;
+ gint delay_seconds = 0;
+ gint ii;
+
+ for (ii = 0; ii < use_requests->len; ii++) {
+ SoupMessage *message = g_ptr_array_index (use_requests, ii);
+
+ if (!message)
+ continue;
+
+ /* Throttling - https://docs.microsoft.com/en-us/graph/throttling */
+ if (message->status_code == 429 ||
+ /*
https://docs.microsoft.com/en-us/graph/best-practices-concept#handling-expected-errors */
+ message->status_code == SOUP_STATUS_SERVICE_UNAVAILABLE) {
+ const gchar *retry_after_str;
+ gint64 retry_after;
+
+ need_retry = TRUE;
+
+ if (!new_requests)
+ new_requests = g_ptr_array_sized_new (use_requests->len);
+
+ g_ptr_array_add (new_requests, message);
+
+ retry_after_str = message->response_headers ?
soup_message_headers_get_one (message->response_headers, "Retry-After") : NULL;
+
+ if (retry_after_str && *retry_after_str)
+ retry_after = g_ascii_strtoll (retry_after_str, NULL, 10);
+ else
+ retry_after = 0;
+
+ if (retry_after > 0)
+ delay_seconds = MAX (delay_seconds, retry_after);
+ else
+ delay_seconds = MAX (delay_seconds, need_retry_seconds);
+ }
+ }
+
+ if (new_requests) {
+ if (delay_seconds)
+ need_retry_seconds = delay_seconds;
+ else if (need_retry_seconds < 120)
+ need_retry_seconds *= 2;
+
+ LOCK (cnc);
+
+ if (cnc->priv->backoff_for_usec < need_retry_seconds * G_USEC_PER_SEC)
+ cnc->priv->backoff_for_usec = need_retry_seconds * G_USEC_PER_SEC;
+
+ UNLOCK (cnc);
+
+ if (use_requests != requests)
+ g_ptr_array_free (use_requests, TRUE);
+
+ use_requests = new_requests;
+ }
+ }
+ }
+
+ if (use_requests != requests)
+ g_ptr_array_free (use_requests, TRUE);
+
+ return success;
+}
+
+/* This can be used as a EM365ConnectionJsonFunc function, it only
+ copies items of 'results' into 'user_data', which is supposed
+ to be a pointer to a GSList *. */
+gboolean
+e_m365_connection_call_gather_into_slist (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned objects from
the server */
+ gpointer user_data, /* expects GSList **, aka pointer to a GSList
*, where it copies the 'results' */
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSList **out_results = user_data, *link;
+
+ g_return_val_if_fail (out_results != NULL, FALSE);
+
+ for (link = (GSList *) results; link; link = g_slist_next (link)) {
+ JsonObject *obj = link->data;
+
+ if (obj)
+ *out_results = g_slist_prepend (*out_results, json_object_ref (obj));
+ }
+
+ return TRUE;
+}
+
+/*
https://docs.microsoft.com/en-us/graph/api/outlookuser-list-mastercategories?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_categories_sync (EM365Connection *cnc,
+ const gchar *user_override,
+ GSList **out_categories, /* EM365Category * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_categories != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "outlook",
+ "masterCategories",
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_categories;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-list-mailfolders?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_list_mail_folders_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *from_path, /* path for the folder to read, NULL for
top user folder */
+ const gchar *select, /* properties to select, nullable */
+ GSList **out_folders, /* EM365MailFolder * - the returned
mailFolder objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_folders != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ NULL,
+ from_path,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_folders;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+gboolean
+e_m365_connection_get_folders_delta_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ EM365FolderKind kind,
+ const gchar *select, /* properties to select, nullable */
+ const gchar *delta_link, /* previous delta link */
+ guint max_page_size, /* 0 for default by the server */
+ EM365ConnectionJsonFunc func, /* function to call with each result
set */
+ gpointer func_user_data, /* user data passed into the 'func' */
+ gchar **out_delta_link,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_delta_link != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ if (delta_link)
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, delta_link, CSM_DEFAULT, NULL);
+
+ if (!message) {
+ const gchar *kind_str = NULL;
+ gchar *uri;
+
+ switch (kind) {
+ case E_M365_FOLDER_KIND_CONTACTS:
+ kind_str = "contactFolders";
+ break;
+ case E_M365_FOLDER_KIND_MAIL:
+ kind_str = "mailFolders";
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+
+ g_return_val_if_fail (kind_str != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ kind_str,
+ NULL,
+ "delta",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+ }
+
+ if (max_page_size > 0) {
+ gchar *prefer_value;
+
+ prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
+
+ soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
+
+ g_free (prefer_value);
+ }
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.json_func = func;
+ rd.func_user_data = func_user_data;
+ rd.out_delta_link = out_delta_link;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/mailfolder-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_mail_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id, /* nullable - then the 'inbox' is used */
+ const gchar *select, /* nullable - properties to select */
+ EM365MailFolder **out_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_folder != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ folder_id ? folder_id : "inbox",
+ NULL,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/mailfolder-post-childfolders?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_mail_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *parent_folder_id, /* NULL for the folder root */
+ const gchar *display_name,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (display_name != NULL, FALSE);
+ g_return_val_if_fail (out_mail_folder != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ parent_folder_id,
+ parent_folder_id ? "childFolders" : NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "displayName", display_name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_mail_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/mailfolder-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_mail_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders", folder_id, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/mailfolder-copy?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/mailfolder-move?view=graph-rest-1.0&tabs=http
+ */
+gboolean
+e_m365_connection_copy_move_mail_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *src_folder_id,
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (src_folder_id != NULL, FALSE);
+ g_return_val_if_fail (des_folder_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ src_folder_id,
+ do_copy ? "copy" : "move",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "destinationId", des_folder_id);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_mail_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/mailfolder-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_rename_mail_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id,
+ const gchar *display_name,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (display_name != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "mailFolders",
+ folder_id,
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "displayName", display_name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_mail_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-delta?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/contact-delta?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_objects_delta_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ EM365FolderKind kind,
+ const gchar *folder_id, /* folder ID to get delta messages in */
+ const gchar *select, /* properties to select, nullable */
+ const gchar *delta_link, /* previous delta link */
+ guint max_page_size, /* 0 for default by the server */
+ EM365ConnectionJsonFunc func, /* function to call with each result
set */
+ gpointer func_user_data, /* user data passed into the 'func' */
+ gchar **out_delta_link,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (out_delta_link != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ if (delta_link)
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, delta_link, CSM_DEFAULT, NULL);
+
+ if (!message) {
+ const gchar *kind_str = NULL, *kind_path_str = NULL;
+ gchar *uri;
+
+ switch (kind) {
+ case E_M365_FOLDER_KIND_CONTACTS:
+ kind_str = "contactFolders";
+ kind_path_str = "contacts";
+ break;
+ case E_M365_FOLDER_KIND_MAIL:
+ kind_str = "mailFolders";
+ kind_path_str = "messages";
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+
+ g_return_val_if_fail (kind_str != NULL && kind_path_str != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ kind_str,
+ folder_id,
+ kind_path_str,
+ "", "delta",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+ }
+
+ if (max_page_size > 0) {
+ gchar *prefer_value;
+
+ prefer_value = g_strdup_printf ("odata.maxpagesize=%u", max_page_size);
+
+ soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
+
+ g_free (prefer_value);
+ }
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.json_func = func;
+ rd.func_user_data = func_user_data;
+ rd.out_delta_link = out_delta_link;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_mail_message_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id,
+ const gchar *message_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (message_id != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ "$value",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable,
error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_mail_message_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id, /* if NULL, then goes to the Drafts
folder */
+ JsonBuilder *mail_message, /* filled mailMessage object */
+ EM365MailMessage **out_created_message, /* free with
json_object_unref() */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (mail_message != NULL, FALSE);
+ g_return_val_if_fail (out_created_message != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ folder_id ? "mailFolders" : "messages",
+ folder_id,
+ folder_id ? "messages" : NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, mail_message);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_message, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-post-attachments?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_add_mail_message_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to
use the account user */
+ const gchar *message_id, /* the message to add it to */
+ JsonBuilder *attachment, /* filled attachment object */
+ gchar **out_attachment_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonObject *added_attachment = NULL;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (attachment != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ "attachments",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, attachment);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
&added_attachment, cancellable, error);
+
+ if (success && added_attachment && out_attachment_id)
+ *out_attachment_id = g_strdup (e_m365_attachment_get_id (added_attachment));
+
+ if (added_attachment)
+ json_object_unref (added_attachment);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-update?view=graph-rest-1.0&tabs=http */
+
+SoupMessage *
+e_m365_connection_prepare_update_mail_message (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *message_id,
+ JsonBuilder *mail_message, /* values to update, as a
mailMessage object */
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+ g_return_val_if_fail (message_id != NULL, NULL);
+ g_return_val_if_fail (mail_message != NULL, NULL);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ NULL,
+ NULL);
+
+ /* The server returns the mailMessage object back, but it can be ignored here */
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return NULL;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, mail_message);
+
+ return message;
+}
+
+gboolean
+e_m365_connection_update_mail_message_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *message_id,
+ JsonBuilder *mail_message, /* values to update, as a mailMessage
object */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (message_id != NULL, FALSE);
+ g_return_val_if_fail (mail_message != NULL, FALSE);
+
+ message = e_m365_connection_prepare_update_mail_message (cnc, user_override, message_id,
mail_message, error);
+
+ if (!message)
+ return FALSE;
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-copy?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/message-move?view=graph-rest-1.0&tabs=http
+ */
+static SoupMessage *
+e_m365_connection_prepare_copy_move_mail_message (EM365Connection *cnc,
+ const gchar *user_override,
+ const gchar *message_id,
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+ g_return_val_if_fail (message_id != NULL, NULL);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ do_copy ? "copy" : "move",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "destinationId", des_folder_id);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ return message;
+}
+
+/* out_des_message_ids: Camel-pooled gchar *, new ids, in the same order as in message_ids; can be partial */
+gboolean
+e_m365_connection_copy_move_mail_messages_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const GSList *message_ids, /* const gchar * */
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ GSList **out_des_message_ids, /* Camel-pooled gchar * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (message_ids != NULL, FALSE);
+ g_return_val_if_fail (des_folder_id != NULL, FALSE);
+ g_return_val_if_fail (out_des_message_ids != NULL, FALSE);
+
+ *out_des_message_ids = NULL;
+
+ if (g_slist_next (message_ids)) {
+ GPtrArray *requests;
+ GSList *link;
+ guint total, done = 0;
+
+ total = g_slist_length ((GSList *) message_ids);
+ requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)),
g_object_unref);
+
+ for (link = (GSList *) message_ids; link && success; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_copy_move_mail_message (cnc, user_override, id,
des_folder_id, do_copy, error);
+
+ if (!message) {
+ success = FALSE;
+ break;
+ }
+
+ g_ptr_array_add (requests, message);
+
+ if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
+ if (requests->len == 1) {
+ JsonObject *response = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &response, cancellable, error);
+
+ if (response) {
+ *out_des_message_ids = g_slist_prepend (*out_des_message_ids,
+ (gpointer) camel_pstring_strdup
(e_m365_mail_message_get_id (response)));
+ json_object_unref (response);
+ } else {
+ success = FALSE;
+ }
+ } else {
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0,
requests, cancellable, error);
+
+ if (success) {
+ guint ii;
+
+ for (ii = 0; success && ii < requests->len; ii++) {
+ JsonNode *node = NULL;
+
+ message = g_ptr_array_index (requests, ii);
+
+ success = e_m365_connection_json_node_from_message
(message, NULL, &node, cancellable, error);
+
+ if (success && node && JSON_NODE_HOLDS_OBJECT (node))
{
+ JsonObject *response;
+
+ response = json_node_get_object (node);
+
+ if (response) {
+ *out_des_message_ids =
g_slist_prepend (*out_des_message_ids,
+ (gpointer)
camel_pstring_strdup (e_m365_mail_message_get_id (response)));
+ } else {
+ success = FALSE;
+ }
+ } else {
+ success = FALSE;
+ }
+
+ if (node)
+ json_node_unref (node);
+ }
+ }
+ }
+
+ g_ptr_array_remove_range (requests, 0, requests->len);
+
+ done += requests->len;
+
+ camel_operation_progress (cancellable, done * 100.0 / total);
+ }
+ }
+
+ g_ptr_array_free (requests, TRUE);
+ } else {
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_copy_move_mail_message (cnc, user_override,
message_ids->data, des_folder_id, do_copy, error);
+
+ if (message) {
+ JsonObject *response = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &response, cancellable, error);
+
+ if (response) {
+ *out_des_message_ids = g_slist_prepend (*out_des_message_ids,
+ (gpointer) camel_pstring_strdup (e_m365_mail_message_get_id
(response)));
+ json_object_unref (response);
+ } else {
+ success = FALSE;
+ }
+
+ g_clear_object (&message);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ *out_des_message_ids = g_slist_reverse (*out_des_message_ids);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-delete?view=graph-rest-1.0&tabs=http */
+
+static SoupMessage *
+e_m365_connection_prepare_delete_mail_message (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *message_id,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+ g_return_val_if_fail (message_id != NULL, NULL);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return NULL;
+ }
+
+ g_free (uri);
+
+ return message;
+}
+
+gboolean
+e_m365_connection_delete_mail_messages_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const GSList *message_ids, /* const gchar * */
+ GSList **out_deleted_ids, /* (transfer container): const gchar
*, borrowed from message_ids */
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (message_ids != NULL, FALSE);
+
+ if (g_slist_next (message_ids)) {
+ GPtrArray *requests;
+ GSList *link, *from_link = (GSList *) message_ids;
+ guint total, done = 0;
+
+ total = g_slist_length ((GSList *) message_ids);
+ requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)),
g_object_unref);
+
+ for (link = (GSList *) message_ids; link && success; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_delete_mail_message (cnc, user_override, id,
error);
+
+ if (!message) {
+ success = FALSE;
+ break;
+ }
+
+ g_ptr_array_add (requests, message);
+
+ if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
+ if (requests->len == 1) {
+ success = m365_connection_send_request_sync (cnc, message, NULL,
e_m365_read_no_response_cb, NULL, cancellable, error);
+ } else {
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0,
requests, cancellable, error);
+ }
+
+ if (success && out_deleted_ids) {
+ while (from_link) {
+ *out_deleted_ids = g_slist_prepend (*out_deleted_ids,
from_link->data);
+
+ if (from_link == link)
+ break;
+
+ from_link = g_slist_next (from_link);
+ }
+ }
+
+ g_ptr_array_remove_range (requests, 0, requests->len);
+ from_link = g_slist_next (link);
+
+ done += requests->len;
+
+ camel_operation_progress (cancellable, done * 100.0 / total);
+ }
+ }
+
+ g_ptr_array_free (requests, TRUE);
+ } else {
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_delete_mail_message (cnc, user_override,
message_ids->data, error);
+
+ if (message) {
+ success = m365_connection_send_request_sync (cnc, message, NULL,
e_m365_read_no_response_cb, NULL, cancellable, error);
+
+ if (success && out_deleted_ids)
+ *out_deleted_ids = g_slist_prepend (*out_deleted_ids, message_ids->data);
+
+ g_clear_object (&message);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ if (out_deleted_ids && *out_deleted_ids && g_slist_next (*out_deleted_ids))
+ *out_deleted_ids = g_slist_reverse (*out_deleted_ids);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_send_mail_message_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *message_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (message_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "messages",
+ message_id,
+ "send",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ soup_message_headers_append (message->request_headers, "Content-Length", "0");
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_send_mail_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account user
*/
+ JsonBuilder *request, /* filled sendMail object */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (request != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "sendMail", NULL, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, request);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/contactfolder-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_contacts_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id, /* nullable - then the default 'contacts'
folder is returned */
+ const gchar *select, /* nullable - properties to select */
+ EM365Folder **out_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_folder != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "contactFolders",
+ folder_id ? folder_id : "contacts",
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0 */
+
+gboolean
+e_m365_connection_get_contact_photo_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ GByteArray **out_photo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (contact_id != NULL, FALSE);
+ g_return_val_if_fail (out_photo != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "contactFolders",
+ folder_id,
+ "contacts",
+ "", contact_id,
+ "", "photo",
+ "", "$value",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_to_byte_array_cb,
out_photo, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/profilephoto-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_update_contact_photo_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ const GByteArray *jpeg_photo, /* nullable, to remove the photo */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "contactFolders",
+ folder_id,
+ "contacts",
+ "", contact_id,
+ "", "photo",
+ "", "$value",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_PUT, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ soup_message_headers_set_content_type (message->request_headers, "image/jpeg", NULL);
+ soup_message_headers_set_content_length (message->request_headers, jpeg_photo ? jpeg_photo->len : 0);
+
+ if (jpeg_photo)
+ soup_message_body_append (message->request_body, SOUP_MEMORY_STATIC, jpeg_photo->data,
jpeg_photo->len);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/contact-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_contact_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ EM365Contact **out_contact,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (folder_id != NULL, FALSE);
+ g_return_val_if_fail (contact_id != NULL, FALSE);
+ g_return_val_if_fail (out_contact != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "contactFolders",
+ folder_id,
+ "contacts",
+ "", contact_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_contact, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_contact_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *folder_id, /* if NULL, then goes to the Drafts folder */
+ JsonBuilder *contact, /* filled contact object */
+ EM365Contact **out_created_contact, /* free with json_object_unref() */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (contact != NULL, FALSE);
+ g_return_val_if_fail (out_created_contact != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ folder_id ? "contactFolders" : "contacts",
+ folder_id,
+ folder_id ? "contacts" : NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, contact);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_contact, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/contact-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_update_contact_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ JsonBuilder *contact, /* values to update, as a contact object */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (contact_id != NULL, FALSE);
+ g_return_val_if_fail (contact != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ folder_id ? "contactFolders" : "contacts",
+ folder_id,
+ folder_id ? "contacts" : contact_id,
+ "", folder_id ? contact_id : NULL,
+ NULL);
+
+ /* The server returns the contact object back, but it can be ignored here */
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, contact);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/contact-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_contact_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (contact_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ folder_id ? "contactFolders" : "contacts",
+ folder_id,
+ folder_id ? "contacts" : contact_id,
+ "", folder_id ? contact_id : NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-list-calendargroups?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_list_calendar_groups_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ GSList **out_groups, /* EM365CalendarGroup * - the returned
calendarGroup objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_groups != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups", NULL, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_groups;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-calendargroups?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_calendar_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *name,
+ EM365CalendarGroup **out_created_group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (out_created_group != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups", NULL, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_group, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_calendar_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id,
+ EM365CalendarGroup **out_group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+ g_return_val_if_fail (out_group != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups",
+ group_id,
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_group, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_update_calendar_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups",
+ group_id,
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_calendar_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups", group_id, NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 */
+
+gboolean
+e_m365_connection_list_calendars_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, calendar group id for group
calendars */
+ const gchar *select, /* properties to select, nullable */
+ GSList **out_calendars, /* EM365Calendar * - the returned calendar
objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_calendars != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_calendars;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendargroup-post-calendars?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_calendar_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ JsonBuilder *calendar,
+ EM365Calendar **out_created_calendar,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar != NULL, FALSE);
+ g_return_val_if_fail (out_created_calendar != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ "calendars",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, calendar);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_calendar, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_calendar_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is
used */
+ const gchar *calendar_id, /* nullable - then the default calendar
is used */
+ const gchar *select, /* nullable - properties to select */
+ EM365Calendar **out_calendar,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_calendar != NULL, FALSE);
+
+ if (group_id && calendar_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups",
+ group_id,
+ "calendars",
+ "", calendar_id,
+ "$select", select,
+ NULL);
+ } else if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "groups",
+ group_id,
+ "calendar",
+ NULL,
+ "$select", select,
+ NULL);
+ } else if (calendar_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendars",
+ calendar_id,
+ NULL,
+ "$select", select,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendar",
+ NULL,
+ NULL,
+ "$select", select,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_calendar, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_update_calendar_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *calendar_id,
+ const gchar *name, /* nullable - to keep the existing name */
+ EM365CalendarColorType color,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+
+ /* Nothing to change */
+ if (!name && (color == E_M365_CALENDAR_COLOR_NOT_SET || color == E_M365_CALENDAR_COLOR_UNKNOWN))
+ return TRUE;
+
+ if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups",
+ group_id,
+ "calendars",
+ "", calendar_id,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendars",
+ calendar_id,
+ NULL,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_calendar_add_name (builder, name);
+ e_m365_calendar_add_color (builder, color);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_calendar_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *calendar_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+
+ if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendarGroups",
+ group_id,
+ "calendars",
+ "", calendar_id,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendars",
+ calendar_id,
+ NULL,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+static void
+m365_connection_prefer_outlook_timezone (SoupMessage *message,
+ const gchar *prefer_outlook_timezone)
+{
+ g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+ if (prefer_outlook_timezone && *prefer_outlook_timezone) {
+ gchar *prefer_value;
+
+ prefer_value = g_strdup_printf ("outlook.timezone=\"%s\"", prefer_outlook_timezone);
+
+ soup_message_headers_append (message->request_headers, "Prefer", prefer_value);
+
+ g_free (prefer_value);
+ }
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_list_events_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, calendar group id for group calendars
*/
+ const gchar *calendar_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_events, /* EM365Event * - the returned event objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (out_events != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+
+ soup_message_headers_append (message->request_headers, "Prefer",
"outlook.body-content-type=\"text\"");
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_events;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/group-post-events?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_create_event_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *calendar_id,
+ JsonBuilder *event,
+ EM365Event **out_created_event,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (out_created_event != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, event);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_event, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-get?view=graph-rest-1.0&tabs=http */
+
+SoupMessage *
+e_m365_connection_prepare_get_event (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+ g_return_val_if_fail (calendar_id != NULL, NULL);
+ g_return_val_if_fail (event_id != NULL, NULL);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return NULL;
+ }
+
+ g_free (uri);
+
+ m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+ soup_message_headers_append (message->request_headers, "Prefer",
"outlook.body-content-type=\"text\"");
+
+ return message;
+}
+
+gboolean
+e_m365_connection_get_event_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account user
*/
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ EM365Event **out_event,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (out_event != NULL, FALSE);
+
+ message = e_m365_connection_prepare_get_event (cnc, user_override, group_id, calendar_id, event_id,
prefer_outlook_timezone, select, error);
+
+ if (!message)
+ return FALSE;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_event, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+gboolean
+e_m365_connection_get_events_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *calendar_id,
+ const GSList *event_ids, /* const gchar * */
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_events, /* EM365Event *, in the same order as event_ids; can
return partial list */
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_ids != NULL, FALSE);
+ g_return_val_if_fail (out_events != NULL, FALSE);
+
+ if (g_slist_next (event_ids)) {
+ GPtrArray *requests;
+ GSList *link;
+ guint total, done = 0;
+
+ total = g_slist_length ((GSList *) event_ids);
+ requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)),
g_object_unref);
+
+ for (link = (GSList *) event_ids; link && success; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_get_event (cnc, user_override, group_id,
calendar_id, id, prefer_outlook_timezone, select, error);
+
+ if (!message) {
+ success = FALSE;
+ break;
+ }
+
+ g_ptr_array_add (requests, message);
+
+ if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
+ if (requests->len == 1) {
+ EM365Event *event = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &event, cancellable, error);
+
+ if (success)
+ *out_events = g_slist_prepend (*out_events, event);
+ } else {
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_V1_0,
requests, cancellable, error);
+
+ if (success) {
+ guint ii;
+
+ for (ii = 0; ii < requests->len && success; ii++) {
+ JsonNode *node = NULL;
+
+ message = requests->pdata[ii];
+ success = e_m365_connection_json_node_from_message
(message, NULL, &node, cancellable, error);
+
+ if (success && node && JSON_NODE_HOLDS_OBJECT (node))
{
+ JsonObject *response;
+
+ response = json_node_get_object (node);
+
+ if (response) {
+ *out_events = g_slist_prepend
(*out_events, json_object_ref (response));
+ } else {
+ success = FALSE;
+ }
+ } else {
+ success = FALSE;
+ }
+
+ if (node)
+ json_node_unref (node);
+ }
+ }
+ }
+
+ g_ptr_array_remove_range (requests, 0, requests->len);
+
+ done += requests->len;
+
+ camel_operation_progress (cancellable, done * 100.0 / total);
+ }
+ }
+
+ g_ptr_array_free (requests, TRUE);
+ } else {
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_get_event (cnc, user_override, group_id, calendar_id,
event_ids->data, prefer_outlook_timezone, select, error);
+
+ if (message) {
+ EM365Event *event = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &event, cancellable, error);
+
+ if (success)
+ *out_events = g_slist_prepend (*out_events, event);
+
+ g_clear_object (&message);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ *out_events = g_slist_reverse (*out_events);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-update?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_update_event_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ JsonBuilder *event,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ NULL);
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, event);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_event_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-accept?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/event-tentativelyaccept?view=graph-rest-1.0&tabs=http
+ https://docs.microsoft.com/en-us/graph/api/event-decline?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_response_event_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ EM365ResponseType response, /* uses only accepted/tentatively
accepted/declined values */
+ const gchar *comment, /* nullable */
+ gboolean send_response,
+ GCancellable *cancellable,
+ GError **error)
+{
+ JsonBuilder *builder;
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (response == E_M365_RESPONSE_ACCEPTED || response ==
E_M365_RESPONSE_TENTATIVELY_ACCEPTED || response == E_M365_RESPONSE_DECLINED, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", response == E_M365_RESPONSE_TENTATIVELY_ACCEPTED ? "tentativelyAccept" :
+ response == E_M365_RESPONSE_DECLINED ? "decline" : "accept",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_nonempty_string_member (builder, "comment", comment);
+ e_m365_json_add_boolean_member (builder, "sendResponse", send_response);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-dismissreminder?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_dismiss_reminder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is used
*/
+ const gchar *calendar_id,
+ const gchar *event_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", "dismissReminder",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-list-attachments?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_list_event_attachments_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_attachments, /* EM365Attachment * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (out_attachments != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", "attachments",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_attachments;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_event_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *attachment_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (attachment_id != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", "attachments",
+ "", attachment_id,
+ "", "$value",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable,
error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/event-post-attachments?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_add_event_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ JsonBuilder *in_attachment,
+ EM365Attachment **out_attachment, /* nullable */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (in_attachment != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", "attachments",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, out_attachment ? CSM_DEFAULT :
CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, in_attachment);
+
+ success = m365_connection_send_request_sync (cnc, message, out_attachment ?
e_m365_read_json_object_response_cb : NULL,
+ out_attachment ? NULL : e_m365_read_no_response_cb, out_attachment, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_delete_event_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *attachment_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (calendar_id != NULL, FALSE);
+ g_return_val_if_fail (event_id != NULL, FALSE);
+ g_return_val_if_fail (attachment_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ group_id ? "calendarGroups" : "calendars",
+ group_id,
+ group_id ? "calendars" : NULL,
+ "", calendar_id,
+ "", "events",
+ "", event_id,
+ "", "attachments",
+ "", attachment_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/calendar-getschedule?view=graph-rest-1.0&tabs=http */
+
+gboolean
+e_m365_connection_get_schedule_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ gint interval_minutes, /* between 5 and 1440, -1 to use the default (30)
*/
+ time_t start_time,
+ time_t end_time,
+ const GSList *email_addresses, /* const gchar * - SMTP addresses to
query */
+ GSList **out_infos, /* EM365ScheduleInformation * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ JsonBuilder *builder;
+ SoupMessage *message;
+ GSList *link;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (email_addresses != NULL, FALSE);
+ g_return_val_if_fail (out_infos != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, NULL,
+ "calendar",
+ "getSchedule",
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+
+ if (interval_minutes > 0)
+ e_m365_json_add_int_member (builder, "interval", interval_minutes);
+
+ e_m365_add_date_time (builder, "startTime", start_time, "UTC");
+ e_m365_add_date_time (builder, "endTime", end_time, "UTC");
+ e_m365_json_begin_array_member (builder, "schedules");
+
+ for (link = (GSList *) email_addresses; link; link = g_slist_next (link)) {
+ const gchar *addr = link->data;
+
+ if (addr && *addr)
+ json_builder_add_string_value (builder, addr);
+ }
+
+ e_m365_json_end_array_member (builder); /* "schedules" */
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_infos;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskgroups?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_groups_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ GSList **out_groups, /* EM365TaskGroup * - the returned
outlookTaskGroup objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_groups != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook", "taskGroups", NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_groups;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskgroups?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_create_task_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *name,
+ EM365TaskGroup **out_created_group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (out_created_group != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook", "taskGroups", NULL, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_group, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id,
+ EM365TaskGroup **out_group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+ g_return_val_if_fail (out_group != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskGroups",
+ group_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_group, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskgroup-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskGroups",
+ group_id,
+ NULL,
+ NULL);
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_group_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (group_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook", "taskGroups", group_id, NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-list-taskfolders?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_folders_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, task group id for group task
folders */
+ const gchar *select, /* properties to select, nullable */
+ GSList **out_folders, /* EM365TaskFolder * - the returned
outlookTaskFolder objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (out_folders != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_folders;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-taskfolders?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_create_task_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is used
*/
+ JsonBuilder *task_folder,
+ EM365TaskFolder **out_created_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder != NULL, FALSE);
+ g_return_val_if_fail (out_created_folder != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", "taskFolders",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, task_folder);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *select, /* nullable - properties to select */
+ EM365TaskFolder **out_task_folder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (out_task_folder != NULL, FALSE);
+
+ if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskGroups",
+ group_id,
+ "", "taskFolders",
+ "", task_folder_id,
+ "$select", select,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskFolders",
+ task_folder_id,
+ "$select", select,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_task_folder, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is
used */
+ const gchar *task_folder_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ JsonBuilder *builder;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskGroups",
+ group_id,
+ "", "taskFolders",
+ "", task_folder_id,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskFolders",
+ task_folder_id,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ builder = json_builder_new_immutable ();
+
+ e_m365_json_begin_object_member (builder, NULL);
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_end_object_member (builder);
+
+ e_m365_connection_set_json_body (message, builder);
+
+ g_object_unref (builder);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_folder_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable - then the default group is
used */
+ const gchar *task_folder_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+
+ if (group_id) {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskGroups",
+ group_id,
+ "", "taskFolders",
+ "", task_folder_id,
+ NULL);
+ } else {
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ "taskFolders",
+ task_folder_id,
+ NULL);
+ }
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktaskfolder-list-tasks?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_tasks_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, task group id for group task folders */
+ const gchar *task_folder_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_tasks, /* EM365Task * - the returned task objects */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (out_tasks != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+ soup_message_headers_append (message->request_headers, "Prefer",
"outlook.body-content-type=\"text\"");
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_tasks;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlookuser-post-tasks?view=graph-rest-beta&tabs=csharp */
+
+gboolean
+e_m365_connection_create_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *task_folder_id,
+ JsonBuilder *task,
+ EM365Task **out_created_task,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task != NULL, FALSE);
+ g_return_val_if_fail (out_created_task != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, task);
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_created_task, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-get?view=graph-rest-beta&tabs=http */
+
+SoupMessage *
+e_m365_connection_prepare_get_task (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GError **error)
+{
+ SoupMessage *message;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), NULL);
+ g_return_val_if_fail (task_folder_id != NULL, NULL);
+ g_return_val_if_fail (task_id != NULL, NULL);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return NULL;
+ }
+
+ g_free (uri);
+
+ m365_connection_prefer_outlook_timezone (message, prefer_outlook_timezone);
+ soup_message_headers_append (message->request_headers, "Prefer",
"outlook.body-content-type=\"text\"");
+
+ return message;
+}
+
+gboolean
+e_m365_connection_get_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account user
*/
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise that
zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ EM365Task **out_task,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (out_task != NULL, FALSE);
+
+ message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id, task_id,
prefer_outlook_timezone, select, error);
+
+ if (!message)
+ return FALSE;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_json_object_response_cb, NULL,
out_task, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+gboolean
+e_m365_connection_get_tasks_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account user
*/
+ const gchar *group_id, /* nullable, then the default group is used */
+ const gchar *task_folder_id,
+ const GSList *task_ids, /* const gchar * */
+ const gchar *prefer_outlook_timezone, /* nullable - then UTC, otherwise
that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_tasks, /* EM365Task *, in the same order as task_ids; can
return partial list */
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_ids != NULL, FALSE);
+ g_return_val_if_fail (out_tasks != NULL, FALSE);
+
+ if (g_slist_next (task_ids)) {
+ GPtrArray *requests;
+ GSList *link;
+ guint total, done = 0;
+
+ total = g_slist_length ((GSList *) task_ids);
+ requests = g_ptr_array_new_full (MIN (E_M365_BATCH_MAX_REQUESTS, MIN (total, 50)),
g_object_unref);
+
+ for (link = (GSList *) task_ids; link && success; link = g_slist_next (link)) {
+ const gchar *id = link->data;
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_get_task (cnc, user_override, group_id,
task_folder_id, id, prefer_outlook_timezone, select, error);
+
+ if (!message) {
+ success = FALSE;
+ break;
+ }
+
+ g_ptr_array_add (requests, message);
+
+ if (requests->len == E_M365_BATCH_MAX_REQUESTS || !link->next) {
+ if (requests->len == 1) {
+ EM365Task *task = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
+
+ if (success)
+ *out_tasks = g_slist_prepend (*out_tasks, task);
+ } else {
+ success = e_m365_connection_batch_request_sync (cnc, E_M365_API_BETA,
requests, cancellable, error);
+
+ if (success) {
+ guint ii;
+
+ for (ii = 0; ii < requests->len && success; ii++) {
+ JsonNode *node = NULL;
+
+ message = requests->pdata[ii];
+ success = e_m365_connection_json_node_from_message
(message, NULL, &node, cancellable, error);
+
+ if (success && node && JSON_NODE_HOLDS_OBJECT (node))
{
+ JsonObject *response;
+
+ response = json_node_get_object (node);
+
+ if (response) {
+ *out_tasks = g_slist_prepend
(*out_tasks, json_object_ref (response));
+ } else {
+ success = FALSE;
+ }
+ } else {
+ success = FALSE;
+ }
+
+ if (node)
+ json_node_unref (node);
+ }
+ }
+ }
+
+ g_ptr_array_remove_range (requests, 0, requests->len);
+
+ done += requests->len;
+
+ camel_operation_progress (cancellable, done * 100.0 / total);
+ }
+ }
+
+ g_ptr_array_free (requests, TRUE);
+ } else {
+ SoupMessage *message;
+
+ message = e_m365_connection_prepare_get_task (cnc, user_override, group_id, task_folder_id,
task_ids->data, prefer_outlook_timezone, select, error);
+
+ if (message) {
+ EM365Task *task = NULL;
+
+ success = m365_connection_send_request_sync (cnc, message,
e_m365_read_json_object_response_cb, NULL, &task, cancellable, error);
+
+ if (success)
+ *out_tasks = g_slist_prepend (*out_tasks, task);
+
+ g_clear_object (&message);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ *out_tasks = g_slist_reverse (*out_tasks);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-update?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_update_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ JsonBuilder *task,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (task != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ NULL);
+
+ message = m365_connection_new_soup_message ("PATCH", uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, task);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-complete?view=graph-rest-beta */
+
+gboolean
+e_m365_connection_complete_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the account
user */
+ const gchar *group_id, /* nullable - then the default group is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "", "complete",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-list-attachments?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_list_task_attachments_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_attachments, /* EM365Attachment * */
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365ResponseData rd;
+ SoupMessage *message;
+ gchar *uri;
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (out_attachments != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "", "attachments",
+ "$select", select,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ memset (&rd, 0, sizeof (EM365ResponseData));
+
+ rd.out_items = out_attachments;
+
+ success = m365_connection_send_request_sync (cnc, message, e_m365_read_valued_response_cb, NULL, &rd,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-get?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_get_task_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *attachment_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (attachment_id != NULL, FALSE);
+ g_return_val_if_fail (func != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "", "attachments",
+ "", attachment_id,
+ "", "$value",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_GET, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, func, func_user_data, cancellable,
error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/outlooktask-post-attachments?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_add_task_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ JsonBuilder *in_attachment,
+ EM365Attachment **out_attachment, /* nullable */
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (in_attachment != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "", "attachments",
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_POST, uri, out_attachment ? CSM_DEFAULT :
CSM_DISABLE_RESPONSE, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ e_m365_connection_set_json_body (message, in_attachment);
+
+ success = m365_connection_send_request_sync (cnc, message, out_attachment ?
e_m365_read_json_object_response_cb : NULL,
+ out_attachment ? NULL : e_m365_read_no_response_cb, out_attachment, cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-beta&tabs=http */
+
+gboolean
+e_m365_connection_delete_task_attachment_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use the
account user */
+ const gchar *group_id, /* nullable, then the default group is
used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *attachment_id,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessage *message;
+ gboolean success;
+ gchar *uri;
+
+ g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
+ g_return_val_if_fail (task_folder_id != NULL, FALSE);
+ g_return_val_if_fail (task_id != NULL, FALSE);
+ g_return_val_if_fail (attachment_id != NULL, FALSE);
+
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_BETA, NULL,
+ "outlook",
+ group_id ? "taskGroups" : "taskFolders",
+ group_id,
+ "", group_id ? "taskFolders" : NULL,
+ "", task_folder_id,
+ "", "tasks",
+ "", task_id,
+ "", "attachments",
+ "", attachment_id,
+ NULL);
+
+ message = m365_connection_new_soup_message (SOUP_METHOD_DELETE, uri, CSM_DEFAULT, error);
+
+ if (!message) {
+ g_free (uri);
+
+ return FALSE;
+ }
+
+ g_free (uri);
+
+ success = m365_connection_send_request_sync (cnc, message, NULL, e_m365_read_no_response_cb, NULL,
cancellable, error);
+
+ g_clear_object (&message);
+
+ return success;
+}
diff --git a/src/Microsoft365/common/e-m365-connection.h b/src/Microsoft365/common/e-m365-connection.h
new file mode 100644
index 00000000..e12a18be
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-connection.h
@@ -0,0 +1,758 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_CONNECTION_H
+#define E_M365_CONNECTION_H
+
+#include <glib-object.h>
+
+#include <libebackend/libebackend.h>
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+
+#include "camel-m365-settings.h"
+#include "e-m365-enums.h"
+#include "e-m365-json-utils.h"
+
+/* Currently, as of 2020-06-17, there is a limitation to 20 requests:
+ https://docs.microsoft.com/en-us/graph/known-issues#json-batching */
+#define E_M365_BATCH_MAX_REQUESTS 20
+
+/* Standard GObject macros */
+#define E_TYPE_M365_CONNECTION \
+ (e_m365_connection_get_type ())
+#define E_M365_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_M365_CONNECTION, EM365Connection))
+#define E_M365_CONNECTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_M365_CONNECTION, EM365ConnectionClass))
+#define E_IS_M365_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_M365_CONNECTION))
+#define E_IS_M365_CONNECTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_M365_CONNECTION))
+#define E_M365_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_M365_CONNECTION))
+
+G_BEGIN_DECLS
+
+typedef enum _EM365ApiVersion {
+ E_M365_API_V1_0,
+ E_M365_API_BETA
+} EM365ApiVersion;
+
+typedef struct _EM365Connection EM365Connection;
+typedef struct _EM365ConnectionClass EM365ConnectionClass;
+typedef struct _EM365ConnectionPrivate EM365ConnectionPrivate;
+
+/* Returns whether can continue */
+typedef gboolean (* EM365ConnectionJsonFunc) (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned
objects from the server */
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error);
+
+typedef gboolean (* EM365ConnectionRawDataFunc) (EM365Connection *cnc,
+ SoupMessage *message,
+ GInputStream *raw_data_stream,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error);
+
+struct _EM365Connection {
+ GObject parent;
+ EM365ConnectionPrivate *priv;
+};
+
+struct _EM365ConnectionClass {
+ GObjectClass parent_class;
+};
+
+gboolean e_m365_connection_util_delta_token_failed
+ (const GError *error);
+
+GType e_m365_connection_get_type (void) G_GNUC_CONST;
+
+EM365Connection *
+ e_m365_connection_new (ESource *source,
+ CamelM365Settings *settings);
+EM365Connection *
+ e_m365_connection_new_for_backend
+ (EBackend *backend,
+ ESourceRegistry *registry,
+ ESource *source,
+ CamelM365Settings *settings);
+EM365Connection *
+ e_m365_connection_new_full (ESource *source,
+ CamelM365Settings *settings,
+ gboolean allow_reuse);
+ESource * e_m365_connection_get_source (EM365Connection *cnc);
+CamelM365Settings *
+ e_m365_connection_get_settings (EM365Connection *cnc);
+guint e_m365_connection_get_concurrent_connections
+ (EM365Connection *cnc);
+void e_m365_connection_set_concurrent_connections
+ (EM365Connection *cnc,
+ guint concurrent_connections);
+GProxyResolver *e_m365_connection_ref_proxy_resolver
+ (EM365Connection *cnc);
+void e_m365_connection_set_proxy_resolver
+ (EM365Connection *cnc,
+ GProxyResolver *proxy_resolver);
+ESoupAuthBearer *
+ e_m365_connection_ref_bearer_auth
+ (EM365Connection *cnc);
+void e_m365_connection_set_bearer_auth
+ (EM365Connection *cnc,
+ ESoupAuthBearer *bearer_auth);
+gboolean e_m365_connection_get_ssl_error_details
+ (EM365Connection *cnc,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors);
+ESourceAuthenticationResult
+ e_m365_connection_authenticate_sync
+ (EM365Connection *cnc,
+ const gchar *user_override,
+ EM365FolderKind kind,
+ const gchar *group_id,
+ const gchar *folder_id,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_disconnect_sync
+ (EM365Connection *cnc,
+ GCancellable *cancellable,
+ GError **error);
+gchar * e_m365_connection_construct_uri (EM365Connection *cnc,
+ gboolean include_user,
+ const gchar *user_override,
+ EM365ApiVersion api_version,
+ const gchar *api_part, /* NULL for 'users', empty string to
skip */
+ const gchar *resource,
+ const gchar *id, /* NULL to skip */
+ const gchar *path,
+ ...) G_GNUC_NULL_TERMINATED;
+gboolean e_m365_connection_json_node_from_message
+ (SoupMessage *message,
+ GInputStream *input_stream,
+ JsonNode **out_node,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_batch_request_sync
+ (EM365Connection *cnc,
+ EM365ApiVersion api_version,
+ GPtrArray *requests, /* SoupMessage * */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_call_gather_into_slist
+ (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned
objects from the server */
+ gpointer user_data, /* expects GSList **, aka pointer to a
GSList *, where it copies the 'results' */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_categories_sync
+ (EM365Connection *cnc,
+ const gchar *user_override,
+ GSList **out_categories, /* EM365Category * */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_mail_folders_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *from_path, /* path for the folder to read, NULL
for top user folder */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_folders, /* EM365MailFolder * - the returned
mailFolder objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_folders_delta_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ EM365FolderKind kind,
+ const gchar *select, /* nullable - properties to select */
+ const gchar *delta_link, /* previous delta link */
+ guint max_page_size, /* 0 for default by the server */
+ EM365ConnectionJsonFunc func, /* function to call with each
result set */
+ gpointer func_user_data, /* user data passed into the 'func'
*/
+ gchar **out_delta_link,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_mail_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id, /* nullable - then the 'inbox' is
used */
+ const gchar *select, /* nullable - properties to select */
+ EM365MailFolder **out_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_mail_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *parent_folder_id, /* NULL for the folder root */
+ const gchar *display_name,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_mail_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_copy_move_mail_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *src_folder_id,
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_rename_mail_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *display_name,
+ EM365MailFolder **out_mail_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_objects_delta_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ EM365FolderKind kind,
+ const gchar *folder_id, /* folder ID to get delta messages
in */
+ const gchar *select, /* nullable - properties to select */
+ const gchar *delta_link, /* previous delta link */
+ guint max_page_size, /* 0 for default by the server */
+ EM365ConnectionJsonFunc func, /* function to call with each
result set */
+ gpointer func_user_data, /* user data passed into the 'func'
*/
+ gchar **out_delta_link,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_mail_message_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *message_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_mail_message_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id, /* if NULL, then goes to the Drafts
folder */
+ JsonBuilder *mail_message, /* filled mailMessage object */
+ EM365MailMessage **out_created_message, /* free with
json_object_unref() */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_add_mail_message_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *message_id, /* the message to add it to */
+ JsonBuilder *attachment, /* filled attachment object */
+ gchar **out_attachment_id,
+ GCancellable *cancellable,
+ GError **error);
+SoupMessage * e_m365_connection_prepare_update_mail_message
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *message_id,
+ JsonBuilder *mail_message, /* values to update, as a
mailMessage object */
+ GError **error);
+gboolean e_m365_connection_update_mail_message_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *message_id,
+ JsonBuilder *mail_message, /* values to update, as a
mailMessage object */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_copy_move_mail_messages_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const GSList *message_ids, /* const gchar * */
+ const gchar *des_folder_id,
+ gboolean do_copy,
+ GSList **out_des_message_ids, /* Camel-pooled gchar *, can
be partial */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_mail_messages_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const GSList *message_ids, /* const gchar * */
+ GSList **out_deleted_ids, /* (transfer container): const
gchar *, borrowed from message_ids */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_send_mail_message_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *message_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_send_mail_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ JsonBuilder *request, /* filled sendMail object */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_contacts_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id, /* nullable - then the default
'contacts' folder is returned */
+ const gchar *select, /* nullable - properties to select */
+ EM365Folder **out_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_contact_photo_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ GByteArray **out_photo,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_contact_photo_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ const GByteArray *jpeg_photo, /* nullable - to remove the
photo */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_contact_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ EM365Contact **out_contact,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_contact_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id, /* if NULL, then goes to the Drafts
folder */
+ JsonBuilder *contact, /* filled contact object */
+ EM365Contact **out_created_contact, /* free with
json_object_unref() */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_contact_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ JsonBuilder *contact, /* values to update, as a contact
object */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_contact_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *folder_id,
+ const gchar *contact_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_calendar_groups_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ GSList **out_groups, /* EM365CalendarGroup * - the returned
calendarGroup objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_calendar_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *name,
+ EM365CalendarGroup **out_created_group,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_calendar_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ EM365CalendarGroup **out_group,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_calendar_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_calendar_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_calendars_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - calendar group for
group calendars */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_calendars, /* EM365Calendar * - the returned
calendar objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_calendar_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ JsonBuilder *calendar,
+ EM365Calendar **out_created_calendar,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_calendar_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id, /* nullable - then the default
calendar is used */
+ const gchar *select, /* nullable - properties to select */
+ EM365Calendar **out_calendar,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_calendar_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *name, /* nullable - to keep the existing name */
+ EM365CalendarColorType color,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_calendar_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_events_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - calendar group for
group calendars */
+ const gchar *calendar_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_events, /* EM365Event * - the returned event
objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_event_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ JsonBuilder *event,
+ EM365Event **out_created_event,
+ GCancellable *cancellable,
+ GError **error);
+SoupMessage * e_m365_connection_prepare_get_event
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GError **error);
+gboolean e_m365_connection_get_event_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ EM365Event **out_event,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_events_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const GSList *event_ids, /* const gchar * */
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_events, /* EM365Event *, in the same order as
event_ids; can return partial list */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_event_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ JsonBuilder *event,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_event_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_response_event_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ EM365ResponseType response, /* uses only
accepted/tentatively accepted/declined values */
+ const gchar *comment, /* nullable */
+ gboolean send_response,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_dismiss_reminder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_event_attachments_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_attachments, /* EM365Attachment * */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_event_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *attachment_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_add_event_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ JsonBuilder *in_attachment,
+ EM365Attachment **out_attachment, /* nullable */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_event_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *calendar_id,
+ const gchar *event_id,
+ const gchar *attachment_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_schedule_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ gint interval_minutes, /* between 5 and 1440, -1 to use the
default (30) */
+ time_t start_time,
+ time_t end_time,
+ const GSList *email_addresses, /* const gchar * - SMTP
addresses to query */
+ GSList **out_infos, /* EM365ScheduleInformation * */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_task_groups_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ GSList **out_groups, /* EM365TaskGroup * - the returned
outlookTaskGroup objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_task_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *name,
+ EM365TaskGroup **out_created_group,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_task_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ EM365TaskGroup **out_group,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_task_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_task_group_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_task_folders_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, task group id for group
task folders */
+ const gchar *select, /* properties to select, nullable */
+ GSList **out_folders, /* EM365TaskFolder * - the returned
outlookTaskFolder objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_task_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *select, /* nullable - properties to select */
+ EM365TaskFolder **out_task_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_task_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ JsonBuilder *task_folder,
+ EM365TaskFolder **out_created_folder,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_task_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *name,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_task_folder_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_tasks_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, task group id for group
task folders */
+ const gchar *task_folder_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_tasks, /* EM365Task * - the returned task
objects */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_create_task_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ JsonBuilder *task,
+ EM365Task **out_created_task,
+ GCancellable *cancellable,
+ GError **error);
+SoupMessage * e_m365_connection_prepare_get_task
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GError **error);
+gboolean e_m365_connection_get_task_sync (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ EM365Task **out_task,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_tasks_sync(EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const GSList *task_ids, /* const gchar * */
+ const gchar *prefer_outlook_timezone, /* nullable - then
UTC, otherwise that zone for the returned times */
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_tasks, /* EM365Task *, in the same order as
task_ids; can return partial list */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_update_task_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ JsonBuilder *task,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_task_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_complete_task_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable - then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_list_task_attachments_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *select, /* nullable - properties to select */
+ GSList **out_attachments, /* EM365Attachment * */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_get_task_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *attachment_id,
+ EM365ConnectionRawDataFunc func,
+ gpointer func_user_data,
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_add_task_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ JsonBuilder *in_attachment,
+ EM365Attachment **out_attachment, /* nullable */
+ GCancellable *cancellable,
+ GError **error);
+gboolean e_m365_connection_delete_task_attachment_sync
+ (EM365Connection *cnc,
+ const gchar *user_override, /* for which user, NULL to use
the account user */
+ const gchar *group_id, /* nullable, then the default group
is used */
+ const gchar *task_folder_id,
+ const gchar *task_id,
+ const gchar *attachment_id,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_M365_CONNECTION_H */
diff --git a/src/Microsoft365/common/e-m365-enums.h b/src/Microsoft365/common/e-m365-enums.h
new file mode 100644
index 00000000..ea5919b6
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-enums.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_ENUMS_H
+#define E_M365_ENUMS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ E_M365_FOLDER_KIND_UNKNOWN,
+ E_M365_FOLDER_KIND_MAIL,
+ E_M365_FOLDER_KIND_CALENDAR,
+ E_M365_FOLDER_KIND_CONTACTS,
+ E_M365_FOLDER_KIND_SEARCH,
+ E_M365_FOLDER_KIND_TASKS,
+ E_M365_FOLDER_KIND_MEMOS
+} EM365FolderKind;
+
+G_END_DECLS
+
+#endif /* E_M365_ENUMS_H */
diff --git a/src/Microsoft365/common/e-m365-json-utils.c b/src/Microsoft365/common/e-m365-json-utils.c
new file mode 100644
index 00000000..9427331d
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-json-utils.c
@@ -0,0 +1,3889 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <stdio.h>
+#include <json-glib/json-glib.h>
+
+#include "e-m365-json-utils.h"
+
+typedef struct _MapData {
+ const gchar *json_value;
+ gint enum_value;
+} MapData;
+
+static gint
+m365_json_utils_json_value_as_enum (const gchar *json_value,
+ const MapData *items,
+ guint n_items,
+ gint not_set_value,
+ gint unknown_value)
+{
+ guint ii;
+
+ if (!json_value)
+ return not_set_value;
+
+ for (ii = 0; ii < n_items; ii++) {
+ if (items[ii].json_value && g_ascii_strcasecmp (items[ii].json_value, json_value) == 0)
+ return items[ii].enum_value;
+ }
+
+ return unknown_value;
+}
+
+static gint
+m365_json_utils_get_json_as_enum (JsonObject *object,
+ const gchar *string_member_name,
+ const MapData *items,
+ guint n_items,
+ gint not_set_value,
+ gint unknown_value)
+{
+ return m365_json_utils_json_value_as_enum (e_m365_json_get_string_member (object, string_member_name,
NULL),
+ items, n_items, not_set_value, unknown_value);
+}
+
+static void
+m365_json_utils_add_enum_as_json (JsonBuilder *builder,
+ const gchar *string_member_name,
+ gint enum_value,
+ const MapData *items,
+ guint n_items,
+ gint not_set_value,
+ gint default_value)
+{
+ const gchar *json_value = NULL, *default_value_str = NULL;
+ guint ii;
+
+ if (enum_value == not_set_value) {
+ if (string_member_name)
+ e_m365_json_add_null_member (builder, string_member_name);
+ return;
+ }
+
+ for (ii = 0; ii < n_items; ii++) {
+ if (items[ii].enum_value == default_value) {
+ default_value_str = items[ii].json_value;
+
+ if (json_value)
+ break;
+ }
+
+ if (items[ii].enum_value == enum_value) {
+ json_value = items[ii].json_value;
+
+ if (default_value_str)
+ break;
+ }
+ }
+
+ if (!json_value) {
+ g_warning ("%s: Failed to find enum value %d for member '%s'", G_STRFUNC, enum_value,
string_member_name);
+ json_value = default_value_str;
+ }
+
+ if (json_value) {
+ if (string_member_name)
+ e_m365_json_add_string_member (builder, string_member_name, json_value);
+ else
+ json_builder_add_string_value (builder, json_value ? json_value : "");
+ }
+}
+
+static MapData attachment_data_type_map[] = {
+ { "#microsoft.graph.fileAttachment", E_M365_ATTACHMENT_DATA_TYPE_FILE },
+ { "#microsoft.graph.itemAttachment", E_M365_ATTACHMENT_DATA_TYPE_ITEM },
+ { "#microsoft.graph.referenceAttachment", E_M365_ATTACHMENT_DATA_TYPE_REFERENCE }
+};
+
+static MapData attendee_map[] = {
+ { "required", E_M365_ATTENDEE_REQUIRED },
+ { "optional", E_M365_ATTENDEE_OPTIONAL },
+ { "resource", E_M365_ATTENDEE_RESOURCE }
+};
+
+static struct _color_map {
+ const gchar *name;
+ const gchar *rgb;
+ EM365CalendarColorType value;
+} color_map[] = {
+ { "auto", NULL, E_M365_CALENDAR_COLOR_AUTO },
+ { "lightBlue", "#0078d4", E_M365_CALENDAR_COLOR_LIGHT_BLUE },
+ { "lightGreen", "#b67dfa", E_M365_CALENDAR_COLOR_LIGHT_GREEN },
+ { "lightOrange","#25c4fe", E_M365_CALENDAR_COLOR_LIGHT_ORANGE },
+ { "lightGray", "#968681", E_M365_CALENDAR_COLOR_LIGHT_GRAY },
+ { "lightYellow","#ffc699", E_M365_CALENDAR_COLOR_LIGHT_YELLOW }, /* Navy in web UI */
+ { "lightTeal", "#fc7c78", E_M365_CALENDAR_COLOR_LIGHT_TEAL },
+ { "lightPink", "#1cff73", E_M365_CALENDAR_COLOR_LIGHT_PINK },
+ { "lightBrown", "#8bb256", E_M365_CALENDAR_COLOR_LIGHT_BROWN }, /* Purple in web UI */
+ { "lightRed", "#3af0e0", E_M365_CALENDAR_COLOR_LIGHT_RED },
+ { "maxColor", NULL, E_M365_CALENDAR_COLOR_MAX_COLOR }
+};
+
+static MapData content_type_map[] = {
+ { "text", E_M365_ITEM_BODY_CONTENT_TYPE_TEXT },
+ { "html", E_M365_ITEM_BODY_CONTENT_TYPE_HTML }
+};
+
+static MapData day_of_week_map[] = {
+ { "sunday", E_M365_DAY_OF_WEEK_SUNDAY },
+ { "monday", E_M365_DAY_OF_WEEK_MONDAY },
+ { "tuesday", E_M365_DAY_OF_WEEK_TUESDAY },
+ { "wednesday", E_M365_DAY_OF_WEEK_WEDNESDAY },
+ { "thursday", E_M365_DAY_OF_WEEK_THURSDAY },
+ { "friday", E_M365_DAY_OF_WEEK_FRIDAY },
+ { "saturday", E_M365_DAY_OF_WEEK_SATURDAY }
+};
+
+static MapData event_type_map[] = {
+ { "singleInstance", E_M365_EVENT_TYPE_SINGLE_INSTANCE },
+ { "occurrence", E_M365_EVENT_TYPE_OCCURRENCE },
+ { "exception", E_M365_EVENT_TYPE_EXCEPTION },
+ { "seriesMaster", E_M365_EVENT_TYPE_SERIES_MASTER }
+};
+
+static MapData flag_status_map[] = {
+ { "notFlagged", E_M365_FOLLOWUP_FLAG_STATUS_NOT_FLAGGED },
+ { "complete", E_M365_FOLLOWUP_FLAG_STATUS_COMPLETE },
+ { "flagged", E_M365_FOLLOWUP_FLAG_STATUS_FLAGGED }
+};
+
+static MapData free_busy_status_map[] = {
+ { "unknown", E_M365_FREE_BUSY_STATUS_UNKNOWN },
+ { "free", E_M365_FREE_BUSY_STATUS_FREE },
+ { "tentative", E_M365_FREE_BUSY_STATUS_TENTATIVE },
+ { "busy", E_M365_FREE_BUSY_STATUS_BUSY },
+ { "oof", E_M365_FREE_BUSY_STATUS_OOF },
+ { "workingElsewhere", E_M365_FREE_BUSY_STATUS_WORKING_ELSEWHERE }
+};
+
+static MapData importance_map[] = {
+ { "low", E_M365_IMPORTANCE_LOW },
+ { "normal", E_M365_IMPORTANCE_NORMAL },
+ { "high", E_M365_IMPORTANCE_HIGH }
+};
+
+static MapData inference_classification_map[] = {
+ { "focused", E_M365_INFERENCE_CLASSIFICATION_FOCUSED },
+ { "other", E_M365_INFERENCE_CLASSIFICATION_OTHER }
+};
+
+static MapData location_type_map[] = {
+ { "default", E_M365_LOCATION_DEFAULT },
+ { "conferenceRoom", E_M365_LOCATION_CONFERENCE_ROOM },
+ { "homeAddress", E_M365_LOCATION_HOME_ADDRESS },
+ { "businessAddress", E_M365_LOCATION_BUSINESS_ADDRESS },
+ { "geoCoordinates", E_M365_LOCATION_GEO_COORDINATES },
+ { "streetAddress", E_M365_LOCATION_STREET_ADDRESS },
+ { "hotel", E_M365_LOCATION_HOTEL },
+ { "restaurant", E_M365_LOCATION_RESTAURANT },
+ { "localBusiness", E_M365_LOCATION_LOCAL_BUSINESS },
+ { "postalAddress", E_M365_LOCATION_POSTAL_ADDRESS }
+};
+
+static MapData meeting_provider_map[] = {
+ { "unknown", E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN },
+ { "skypeForBusiness", E_M365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_BUSINESS },
+ { "skypeForConsumer", E_M365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_CONSUMER },
+ { "teamsForBusiness", E_M365_ONLINE_MEETING_PROVIDER_TEAMS_FOR_BUSINESS }
+};
+
+static MapData phone_map[] = {
+ { "home", E_M365_PHONE_HOME },
+ { "business", E_M365_PHONE_BUSINESS },
+ { "mobile", E_M365_PHONE_MOBILE },
+ { "other", E_M365_PHONE_OTHER },
+ { "assistant", E_M365_PHONE_ASSISTANT },
+ { "homeFax", E_M365_PHONE_HOMEFAX },
+ { "businessFax",E_M365_PHONE_BUSINESSFAX },
+ { "otherFax", E_M365_PHONE_OTHERFAX },
+ { "pager", E_M365_PHONE_PAGER },
+ { "radio", E_M365_PHONE_RADIO }
+};
+
+static MapData recurrence_pattern_map[] = {
+ { "daily", E_M365_RECURRENCE_PATTERN_DAILY },
+ { "weekly", E_M365_RECURRENCE_PATTERN_WEEKLY },
+ { "absoluteMonthly", E_M365_RECURRENCE_PATTERN_ABSOLUTE_MONTHLY },
+ { "relativeMonthly", E_M365_RECURRENCE_PATTERN_RELATIVE_MONTHLY },
+ { "absoluteYearly", E_M365_RECURRENCE_PATTERN_ABSOLUTE_YEARLY },
+ { "relativeYearly", E_M365_RECURRENCE_PATTERN_RELATIVE_YEARLY }
+};
+
+static MapData recurrence_range_map[] = {
+ { "endDate", E_M365_RECURRENCE_RANGE_ENDDATE },
+ { "noEnd", E_M365_RECURRENCE_RANGE_NOEND },
+ { "numbered", E_M365_RECURRENCE_RANGE_NUMBERED }
+};
+
+static MapData response_map[] = {
+ { "None", E_M365_RESPONSE_NONE },
+ { "Organizer", E_M365_RESPONSE_ORGANIZER },
+ { "TentativelyAccepted",E_M365_RESPONSE_TENTATIVELY_ACCEPTED },
+ { "Accepted", E_M365_RESPONSE_ACCEPTED },
+ { "Declined", E_M365_RESPONSE_DECLINED },
+ { "NotResponded", E_M365_RESPONSE_NOT_RESPONDED }
+};
+
+static MapData sensitivity_map[] = {
+ { "normal", E_M365_SENSITIVITY_NORMAL },
+ { "personal", E_M365_SENSITIVITY_PERSONAL },
+ { "private", E_M365_SENSITIVITY_PRIVATE },
+ { "confidential", E_M365_SENSITIVITY_CONFIDENTIAL }
+};
+
+static MapData status_map[] = {
+ { "notStarted", E_M365_STATUS_NOT_STARTED },
+ { "inProgress", E_M365_STATUS_IN_PROGRESS },
+ { "completed", E_M365_STATUS_COMPLETED },
+ { "waitingOnOthers", E_M365_STATUS_WAITING_ON_OTHERS },
+ { "deferred", E_M365_STATUS_DEFERRED }
+};
+
+static MapData week_index_map[] = {
+ { "first", E_M365_WEEK_INDEX_FIRST },
+ { "second", E_M365_WEEK_INDEX_SECOND },
+ { "third", E_M365_WEEK_INDEX_THIRD },
+ { "fourth", E_M365_WEEK_INDEX_FOURTH },
+ { "last", E_M365_WEEK_INDEX_LAST }
+};
+
+const gchar *
+e_m365_calendar_color_to_rgb (EM365CalendarColorType color)
+{
+ gint ii;
+
+ for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+ if (color == color_map[ii].value)
+ return color_map[ii].rgb;
+ }
+
+ return NULL;
+}
+
+EM365CalendarColorType
+e_m365_rgb_to_calendar_color (const gchar *rgb)
+{
+ EM365CalendarColorType res;
+ gint ii, rr, gg, bb;
+ gdouble distance, res_distance = -1.0;
+
+ if (!rgb || !*rgb)
+ return E_M365_CALENDAR_COLOR_NOT_SET;
+
+ for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+ if (color_map[ii].rgb && g_ascii_strcasecmp (color_map[ii].rgb, rgb) == 0)
+ return color_map[ii].value;
+ }
+
+ /* When exact match did not work, approximate to the closest */
+
+ if (sscanf (rgb, "#%02x%02x%02x", &rr, &gg, &bb) != 3)
+ return E_M365_CALENDAR_COLOR_UNKNOWN;
+
+ distance = (rr * rr) + (gg * gg) + (bb * bb);
+ res = E_M365_CALENDAR_COLOR_UNKNOWN;
+
+ for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+ if (color_map[ii].rgb && sscanf (color_map[ii].rgb, "#%02x%02x%02x", &rr, &gg, &bb) == 3) {
+ gdouble candidate_distance;
+
+ candidate_distance = (rr * rr) + (gg * gg) + (bb * bb) - distance;
+
+ if (candidate_distance < 0.0)
+ candidate_distance *= -1.0;
+
+ if (!ii || candidate_distance < res_distance) {
+ res_distance = candidate_distance;
+ res = color_map[ii].value;
+ }
+ }
+ }
+
+ return res;
+}
+
+JsonArray *
+e_m365_json_get_array_member (JsonObject *object,
+ const gchar *member_name)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, NULL);
+ g_return_val_if_fail (member_name != NULL, NULL);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return NULL;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_ARRAY (node), NULL);
+
+ return json_node_get_array (node);
+}
+
+void
+e_m365_json_begin_array_member (JsonBuilder *builder,
+ const gchar *member_name)
+{
+ if (member_name && *member_name)
+ json_builder_set_member_name (builder, member_name);
+
+ json_builder_begin_array (builder);
+}
+
+void
+e_m365_json_end_array_member (JsonBuilder *builder)
+{
+ json_builder_end_array (builder);
+}
+
+gboolean
+e_m365_json_get_boolean_member (JsonObject *object,
+ const gchar *member_name,
+ gboolean default_value)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, default_value);
+ g_return_val_if_fail (member_name != NULL, default_value);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return default_value;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+ return json_node_get_boolean (node);
+}
+
+void
+e_m365_json_add_boolean_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gboolean value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ json_builder_set_member_name (builder, member_name);
+ json_builder_add_boolean_value (builder, value);
+}
+
+gdouble
+e_m365_json_get_double_member (JsonObject *object,
+ const gchar *member_name,
+ gdouble default_value)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, default_value);
+ g_return_val_if_fail (member_name != NULL, default_value);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return default_value;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+ return json_node_get_double (node);
+}
+
+void
+e_m365_json_add_double_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gdouble value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ json_builder_set_member_name (builder, member_name);
+ json_builder_add_double_value (builder, value);
+}
+
+gint64
+e_m365_json_get_int_member (JsonObject *object,
+ const gchar *member_name,
+ gint64 default_value)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, default_value);
+ g_return_val_if_fail (member_name != NULL, default_value);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return default_value;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+ return json_node_get_int (node);
+}
+
+void
+e_m365_json_add_int_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gint64 value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ json_builder_set_member_name (builder, member_name);
+ json_builder_add_int_value (builder, value);
+}
+
+gboolean
+e_m365_json_get_null_member (JsonObject *object,
+ const gchar *member_name,
+ gboolean default_value)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, default_value);
+ g_return_val_if_fail (member_name != NULL, default_value);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node)
+ return default_value;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_NULL (node), default_value);
+
+ return json_node_is_null (node);
+}
+
+void
+e_m365_json_add_null_member (JsonBuilder *builder,
+ const gchar *member_name)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ json_builder_set_member_name (builder, member_name);
+ json_builder_add_null_value (builder);
+}
+
+JsonObject *
+e_m365_json_get_object_member (JsonObject *object,
+ const gchar *member_name)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, NULL);
+ g_return_val_if_fail (member_name != NULL, NULL);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return NULL;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (node), NULL);
+
+ return json_node_get_object (node);
+}
+
+void
+e_m365_json_begin_object_member (JsonBuilder *builder,
+ const gchar *member_name)
+{
+ if (member_name && *member_name)
+ json_builder_set_member_name (builder, member_name);
+
+ json_builder_begin_object (builder);
+}
+
+void
+e_m365_json_end_object_member (JsonBuilder *builder)
+{
+ json_builder_end_object (builder);
+}
+
+const gchar *
+e_m365_json_get_string_member (JsonObject *object,
+ const gchar *member_name,
+ const gchar *default_value)
+{
+ JsonNode *node;
+
+ g_return_val_if_fail (object != NULL, default_value);
+ g_return_val_if_fail (member_name != NULL, default_value);
+
+ node = json_object_get_member (object, member_name);
+
+ if (!node || JSON_NODE_HOLDS_NULL (node))
+ return default_value;
+
+ g_return_val_if_fail (JSON_NODE_HOLDS_VALUE (node), default_value);
+
+ return json_node_get_string (node);
+}
+
+void
+e_m365_json_add_string_member (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ json_builder_set_member_name (builder, member_name);
+ json_builder_add_string_value (builder, value ? value : "");
+}
+
+void
+e_m365_json_add_nonempty_string_member (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ if (value && *value)
+ e_m365_json_add_string_member (builder, member_name, value);
+}
+
+void
+e_m365_json_add_nonempty_or_null_string_member (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value)
+{
+ g_return_if_fail (member_name && *member_name);
+
+ if (value && *value)
+ e_m365_json_add_string_member (builder, member_name, value);
+ else
+ e_m365_json_add_null_member (builder, member_name);
+}
+
+EM365Date
+e_m365_date_get (JsonObject *object,
+ const gchar *member_name)
+{
+ const gchar *value;
+ gint year = 0, month = 0, day = 0;
+
+ value = e_m365_json_get_string_member (object, member_name, NULL);
+
+ if (!value || !*value)
+ return -1;
+
+ if (sscanf (value, "%04d-%02d-%02d", &year, &month, &day) != 3) {
+ g_warning ("%s: Failed to decode date '%s' of member '%s'", G_STRFUNC, value, member_name);
+ return -1;
+ }
+
+ return e_m365_date_encode (year, month, day);
+}
+
+void
+e_m365_add_date (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365Date value)
+{
+ gint year, month, day;
+
+ if (e_m365_date_decode (value, &year, &month, &day)) {
+ gchar buff[128];
+
+ g_snprintf (buff, sizeof (buff), "%04d-%02d-%02d", year, month, day);
+ e_m365_json_add_string_member (builder, member_name, buff);
+ }
+}
+
+gboolean
+e_m365_date_decode (EM365Date dt,
+ gint *out_year,
+ gint *out_month,
+ gint *out_day)
+{
+ g_return_val_if_fail (out_year != NULL, FALSE);
+ g_return_val_if_fail (out_month != NULL, FALSE);
+ g_return_val_if_fail (out_day != NULL, FALSE);
+
+ if (dt <= 0)
+ return FALSE;
+
+ *out_year = dt % 10000;
+ *out_month = (dt / 10000) % 100;
+ *out_day = (dt / 1000000) % 100;
+
+ return *out_year > 1000 &&
+ *out_month >= 1 && *out_month <= 12 &&
+ *out_day >= 1 && *out_day <= 31;
+}
+
+EM365Date
+e_m365_date_encode (gint year,
+ gint month,
+ gint day)
+{
+ g_return_val_if_fail (year > 0 && year < 10000, -1);
+ g_return_val_if_fail (month >= 1 && month <= 12, -1);
+ g_return_val_if_fail (day >= 1 && day <= 31, -1);
+
+ return year + (10000 * month) + (1000000 * day);
+}
+
+EM365TimeOfDay
+e_m365_time_of_day_get (JsonObject *object,
+ const gchar *member_name)
+{
+ const gchar *value;
+ gint hour = 0, minute = 0, second = 0, fraction = 0;
+
+ value = e_m365_json_get_string_member (object, member_name, NULL);
+
+ if (!value || !*value)
+ return -1;
+
+ if (sscanf (value, "%02d:%02d:%02d.%07d", &hour, &minute, &second, &fraction) != 4) {
+ g_warning ("%s: Failed to decode timeOfDay '%s' of member '%s'", G_STRFUNC, value,
member_name);
+ return -1;
+ }
+
+ return e_m365_time_of_day_encode (hour, minute, second, fraction);
+}
+
+void
+e_m365_add_time_of_day (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365TimeOfDay value)
+{
+ gint hour, minute, second, fraction;
+
+ if (e_m365_time_of_day_decode (value, &hour, &minute, &second, &fraction)) {
+ gchar buff[128];
+
+ g_snprintf (buff, sizeof (buff), "%02d:%02d:%02d.%07d", hour, minute, second, fraction);
+ e_m365_json_add_string_member (builder, member_name, buff);
+ }
+}
+
+gboolean
+e_m365_time_of_day_decode (EM365TimeOfDay tod,
+ gint *out_hour,
+ gint *out_minute,
+ gint *out_second,
+ gint *out_fraction)
+{
+ g_return_val_if_fail (out_hour != NULL, FALSE);
+ g_return_val_if_fail (out_minute != NULL, FALSE);
+ g_return_val_if_fail (out_second != NULL, FALSE);
+ g_return_val_if_fail (out_fraction != NULL, FALSE);
+
+ if (tod <= 0)
+ return FALSE;
+
+ *out_hour = tod % 100;
+ *out_minute = (tod / 100) % 100;
+ *out_second = (tod / 10000) % 100;
+ *out_fraction = tod / 1000000;
+
+ return *out_hour >= 0 && *out_hour < 24 &&
+ *out_minute >= 0 && *out_minute < 60 &&
+ *out_second >= 0 && *out_second < 60;
+}
+
+EM365TimeOfDay
+e_m365_time_of_day_encode (gint hour,
+ gint minute,
+ gint second,
+ gint fraction)
+{
+ g_return_val_if_fail (hour >= 0 && hour < 24, -1);
+ g_return_val_if_fail (minute >= 0 && minute < 60, -1);
+ g_return_val_if_fail (second >= 0 && second < 60, -1);
+ g_return_val_if_fail (fraction >= 0 && fraction < 10000000, -1);
+
+ return ((EM365TimeOfDay) hour) + (100L * minute) + (10000L * second) + (1000000L * fraction);
+}
+
+time_t
+e_m365_get_date_time_offset_member (JsonObject *object,
+ const gchar *member_name)
+{
+ const gchar *value;
+ time_t res = (time_t) 0;
+
+ value = e_m365_json_get_string_member (object, member_name, NULL);
+
+ if (value) {
+ GDateTime *dt;
+
+ dt = g_date_time_new_from_iso8601 (value, NULL);
+
+ if (!dt) {
+ gint len = strlen (value);
+
+ /* 2020-07-14T00:00:00.0000000 , eventually with 'Z' at the end */
+ if (len == 27 && value[4] == '-' && value[7] == '-' && value[10] == 'T' && value[13]
== ':' && value[16] == ':' && value[19] == '.') {
+ gchar tmp[32];
+
+ strncpy (tmp, value, 27);
+ tmp[27] = 'Z';
+ tmp[28] = '\0';
+
+ dt = g_date_time_new_from_iso8601 (tmp, NULL);
+ }
+ }
+
+ if (dt) {
+ res = (time_t) g_date_time_to_unix (dt);
+ g_date_time_unref (dt);
+ }
+ }
+
+ return res;
+}
+
+static void
+e_m365_add_date_time_offset_member_ex (JsonBuilder *builder,
+ const gchar *member_name,
+ time_t value,
+ gboolean with_utc_zone_char)
+{
+ GDateTime *dt;
+ gchar *value_str;
+
+ if (value <= (time_t) 0) {
+ e_m365_json_add_null_member (builder, member_name);
+ return;
+ }
+
+ dt = g_date_time_new_from_unix_utc (value);
+ g_return_if_fail (dt != NULL);
+
+ value_str = g_date_time_format_iso8601 (dt);
+
+ if (value_str && !with_utc_zone_char) {
+ gchar *z_pos;
+
+ z_pos = strrchr (value_str, 'Z');
+
+ if (z_pos)
+ *z_pos = '\0';
+ }
+
+ e_m365_json_add_string_member (builder, member_name, value_str);
+
+ g_date_time_unref (dt);
+ g_free (value_str);
+}
+
+void
+e_m365_add_date_time_offset_member (JsonBuilder *builder,
+ const gchar *member_name,
+ time_t value)
+{
+ e_m365_add_date_time_offset_member_ex (builder, member_name, value, TRUE);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0 */
+
+time_t
+e_m365_date_time_get_date_time (EM365DateTimeWithZone *datetime)
+{
+ return e_m365_get_date_time_offset_member (datetime, "dateTime");
+}
+
+const gchar *
+e_m365_date_time_get_time_zone (EM365DateTimeWithZone *datetime)
+{
+ return e_m365_json_get_string_member (datetime, "timeZone", NULL);
+}
+
+void
+e_m365_add_date_time (JsonBuilder *builder,
+ const gchar *member_name,
+ time_t date_time,
+ const gchar *zone)
+{
+ g_return_if_fail (member_name != NULL);
+
+ if (date_time <= (time_t) 0) {
+ e_m365_json_add_null_member (builder, member_name);
+ return;
+ }
+
+ e_m365_json_begin_object_member (builder, member_name);
+
+ e_m365_add_date_time_offset_member_ex (builder, "dateTime", date_time, FALSE);
+ e_m365_json_add_string_member (builder, "timeZone", (zone && *zone) ? zone : "UTC");
+
+ e_m365_json_end_object_member (builder);
+}
+
+/* https://docs.microsoft.com/en-us/graph/delta-query-overview */
+
+gboolean
+e_m365_delta_is_removed_object (JsonObject *object)
+{
+ return json_object_has_member (object, "@removed");
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlookcategory?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_category_get_display_name (EM365Category *category)
+{
+ return e_m365_json_get_string_member (category, "displayName", NULL);
+}
+
+const gchar *
+e_m365_category_get_id (EM365Category *category)
+{
+ return e_m365_json_get_string_member (category, "id", NULL);
+}
+
+const gchar *
+e_m365_category_get_color (EM365Category *category)
+{
+ const gchar *colors_array[] = {
+ "#ff1a36", /* Red */
+ "#ff8c00", /* Orange */
+ "#f4b10b", /* Peach */
+ "#fff100", /* Yellow */
+ "#009e48", /* Green */
+ "#00b294", /* Teal */
+ "#89933f", /* Olive */
+ "#00bcf2", /* Blue */
+ "#8e69df", /* Purple */
+ "#f30092", /* Maroon */
+ "#6c7e9a", /* Steel */
+ "#425066", /* DarkSteel */
+ "#969696", /* Gray */
+ "#525552", /* DarkGray */
+ "#282828", /* Black */
+ "#a00023", /* DarkRed */
+ "#c45502", /* DarkOrange */
+ "#af7000", /* DarkPeach */
+ "#b59b02", /* DarkYellow */
+ "#176002", /* DarkGreen */
+ "#00725c", /* DarkTeal */
+ "#5c6022", /* DarkOlive */
+ "#036393", /* DarkBlue */
+ "#422f8e", /* DarkPurple */
+ "#960269" /* DarkMaroon */
+ };
+ const gchar *color_str;
+ gchar *enptr = NULL;
+ gint color_index;
+
+ color_str = e_m365_json_get_string_member (category, "color", NULL);
+
+ if (!color_str ||
+ g_ascii_strcasecmp (color_str, "None") == 0 ||
+ g_ascii_strncasecmp (color_str, "preset", 6) != 0)
+ return NULL;
+
+ color_index = (gint) g_ascii_strtoll (color_str + 6, &enptr, 10);
+
+ if (enptr != color_str && color_index >= 0 && color_index < G_N_ELEMENTS (colors_array))
+ return colors_array[color_index];
+
+ return NULL;
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0
+ https://docs.microsoft.com/en-us/graph/api/resources/contactfolder?view=graph-rest-1.0
+ */
+
+const gchar *
+e_m365_folder_get_id (EM365Folder *folder)
+{
+ return e_m365_json_get_string_member (folder, "id", NULL);
+}
+
+const gchar *
+e_m365_folder_get_parent_folder_id (EM365Folder *folder)
+{
+ return e_m365_json_get_string_member (folder, "parentFolderId", NULL);
+}
+
+const gchar *
+e_m365_folder_get_display_name (EM365Folder *folder)
+{
+ return e_m365_json_get_string_member (folder, "displayName", NULL);
+}
+
+gint32
+e_m365_mail_folder_get_child_folder_count (EM365MailFolder *folder)
+{
+ return (gint32) e_m365_json_get_int_member (folder, "childFolderCount", 0);
+}
+
+gint32
+e_m365_mail_folder_get_total_item_count (EM365MailFolder *folder)
+{
+ return (gint32) e_m365_json_get_int_member (folder, "totalItemCount", 0);
+}
+
+gint32
+e_m365_mail_folder_get_unread_item_count (EM365MailFolder *folder)
+{
+ return (gint32) e_m365_json_get_int_member (folder, "unreadItemCount", 0);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/recipient?view=graph-rest-1.0
+ https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0
+ */
+const gchar *
+e_m365_recipient_get_name (EM365Recipient *recipient)
+{
+ JsonObject *email_address;
+
+ email_address = e_m365_json_get_object_member (recipient, "emailAddress");
+
+ if (!email_address)
+ return NULL;
+
+ return e_m365_json_get_string_member (email_address, "name", NULL);
+}
+
+const gchar *
+e_m365_recipient_get_address (EM365Recipient *recipient)
+{
+ JsonObject *email_address;
+
+ email_address = e_m365_json_get_object_member (recipient, "emailAddress");
+
+ if (!email_address)
+ return NULL;
+
+ return e_m365_json_get_string_member (email_address, "address", NULL);
+}
+
+void
+e_m365_add_recipient (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *name,
+ const gchar *address)
+{
+ g_return_if_fail ((name && *name) || (address && *address));
+
+ e_m365_json_begin_object_member (builder, member_name);
+ e_m365_json_begin_object_member (builder, "emailAddress");
+
+ e_m365_json_add_nonempty_string_member (builder, "name", name);
+ e_m365_json_add_nonempty_string_member (builder, "address", address);
+
+ e_m365_json_end_object_member (builder); /* emailAddress */
+ e_m365_json_end_object_member (builder); /* member_name */
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/internetmessageheader?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_internet_message_header_get_name (EM365InternetMessageHeader *header)
+{
+ return e_m365_json_get_string_member (header, "name", NULL);
+}
+
+const gchar *
+e_m365_internet_message_header_get_value (EM365InternetMessageHeader *header)
+{
+ return e_m365_json_get_string_member (header, "value", NULL);
+}
+
+void
+e_m365_add_internet_message_header (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *value)
+{
+ g_return_if_fail (name && *name);
+ g_return_if_fail (value);
+
+ json_builder_begin_object (builder);
+
+ if (value && (*value == ' ' || *value == '\t'))
+ value++;
+
+ e_m365_json_add_string_member (builder, "name", name);
+ e_m365_json_add_string_member (builder, "value", value);
+
+ json_builder_end_object (builder);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/followupflag?view=graph-rest-1.0 */
+
+EM365DateTimeWithZone *
+e_m365_followup_flag_get_completed_date_time (EM365FollowupFlag *flag)
+{
+ return e_m365_json_get_object_member (flag, "completedDateTime");
+}
+
+void
+e_m365_followup_flag_add_completed_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "completedDateTime", date_time, zone);
+}
+
+EM365DateTimeWithZone *
+e_m365_followup_flag_get_due_date_time (EM365FollowupFlag *flag)
+{
+ return e_m365_json_get_object_member (flag, "dueDateTime");
+}
+
+void
+e_m365_followup_flag_add_due_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "dueDateTime", date_time, zone);
+}
+
+EM365FollowupFlagStatusType
+e_m365_followup_flag_get_flag_status (EM365FollowupFlag *flag)
+{
+ return m365_json_utils_get_json_as_enum (flag, "flagStatus",
+ flag_status_map, G_N_ELEMENTS (flag_status_map),
+ E_M365_FOLLOWUP_FLAG_STATUS_NOT_SET,
+ E_M365_FOLLOWUP_FLAG_STATUS_UNKNOWN);
+}
+
+void
+e_m365_followup_flag_add_flag_status (JsonBuilder *builder,
+ EM365FollowupFlagStatusType status)
+{
+ m365_json_utils_add_enum_as_json (builder, "flagStatus", status,
+ flag_status_map, G_N_ELEMENTS (flag_status_map),
+ E_M365_FOLLOWUP_FLAG_STATUS_NOT_SET,
+ E_M365_FOLLOWUP_FLAG_STATUS_NOT_FLAGGED);
+}
+
+EM365DateTimeWithZone *
+e_m365_followup_flag_get_start_date_time (EM365FollowupFlag *flag)
+{
+ return e_m365_json_get_object_member (flag, "startDateTime");
+}
+
+void
+e_m365_followup_flag_add_start_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "startDateTime", date_time, zone);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/itembody?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_item_body_get_content (EM365ItemBody *item_body)
+{
+ return e_m365_json_get_string_member (item_body, "content", NULL);
+}
+
+EM365ItemBodyContentTypeType
+e_m365_item_body_get_content_type (EM365ItemBody *item_body)
+{
+ return m365_json_utils_get_json_as_enum (item_body, "contentType",
+ content_type_map, G_N_ELEMENTS (content_type_map),
+ E_M365_ITEM_BODY_CONTENT_TYPE_NOT_SET,
+ E_M365_ITEM_BODY_CONTENT_TYPE_UNKNOWN);
+}
+
+void
+e_m365_add_item_body (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content)
+{
+ g_return_if_fail (member_name != NULL);
+
+ if (content_type == E_M365_ITEM_BODY_CONTENT_TYPE_NOT_SET || !content) {
+ e_m365_json_add_null_member (builder, member_name);
+ return;
+ }
+
+ e_m365_json_begin_object_member (builder, member_name);
+
+ m365_json_utils_add_enum_as_json (builder, "contentType", content_type,
+ content_type_map, G_N_ELEMENTS (content_type_map),
+ E_M365_ITEM_BODY_CONTENT_TYPE_NOT_SET,
+ E_M365_ITEM_BODY_CONTENT_TYPE_TEXT);
+
+ e_m365_json_add_string_member (builder, "content", content);
+
+ e_m365_json_end_object_member (builder);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0 */
+
+JsonArray * /* EM365Recipient * */
+e_m365_mail_message_get_bcc_recipients (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "bccRecipients");
+}
+
+void
+e_m365_mail_message_begin_bcc_recipients (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "bccRecipients");
+}
+
+void
+e_m365_mail_message_end_bcc_recipients (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+EM365ItemBody *
+e_m365_mail_message_get_body (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "body");
+}
+
+void
+e_m365_mail_message_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content)
+{
+ e_m365_add_item_body (builder, "body", content_type, content);
+}
+
+const gchar *
+e_m365_mail_message_get_body_preview (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "bodyPreview", NULL);
+}
+
+JsonArray * /* const gchar * */
+e_m365_mail_message_get_categories (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "categories");
+}
+
+void
+e_m365_mail_message_begin_categories (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_m365_mail_message_end_categories (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_mail_message_add_category (JsonBuilder *builder,
+ const gchar *category)
+{
+ g_return_if_fail (category && *category);
+
+ json_builder_add_string_value (builder, category);
+}
+
+JsonArray * /* EM365Recipient * */
+e_m365_mail_message_get_cc_recipients (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "ccRecipients");
+}
+
+void
+e_m365_mail_message_begin_cc_recipients (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "ccRecipients");
+}
+
+void
+e_m365_mail_message_end_cc_recipients (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+const gchar *
+e_m365_mail_message_get_change_key (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_mail_message_get_conversation_id (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "conversationId", NULL);
+}
+
+JsonObject * /* Edm.Binary */
+e_m365_mail_message_get_conversation_index (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "conversationIndex");
+}
+
+time_t
+e_m365_mail_message_get_created_date_time (EM365MailMessage *mail)
+{
+ return e_m365_get_date_time_offset_member (mail, "createdDateTime");
+}
+
+EM365FollowupFlag *
+e_m365_mail_message_get_flag (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "flag");
+}
+
+void
+e_m365_mail_message_begin_flag (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "flag");
+}
+
+void
+e_m365_mail_message_end_flag (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+EM365Recipient *
+e_m365_mail_message_get_from (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "from");
+}
+
+void
+e_m365_mail_message_add_from (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address)
+{
+ e_m365_add_recipient (builder, "from", name, address);
+}
+
+gboolean
+e_m365_mail_message_get_has_attachments (EM365MailMessage *mail)
+{
+ return e_m365_json_get_boolean_member (mail, "hasAttachments", FALSE);
+}
+
+const gchar *
+e_m365_mail_message_get_id (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "id", NULL);
+}
+
+EM365ImportanceType
+e_m365_mail_message_get_importance (EM365MailMessage *mail)
+{
+ return m365_json_utils_get_json_as_enum (mail, "importance",
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_UNKNOWN);
+}
+
+void
+e_m365_mail_message_add_importance (JsonBuilder *builder,
+ EM365ImportanceType importance)
+{
+ m365_json_utils_add_enum_as_json (builder, "importance", importance,
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_NOT_SET);
+}
+
+EM365InferenceClassificationType
+e_m365_mail_message_get_inference_classification (EM365MailMessage *mail)
+{
+ return m365_json_utils_get_json_as_enum (mail, "inferenceClassification",
+ inference_classification_map, G_N_ELEMENTS (inference_classification_map),
+ E_M365_INFERENCE_CLASSIFICATION_NOT_SET,
+ E_M365_INFERENCE_CLASSIFICATION_UNKNOWN);
+}
+
+JsonArray * /* EM365InternetMessageHeader * */
+e_m365_mail_message_get_internet_message_headers (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "internetMessageHeaders");
+}
+
+void
+e_m365_mail_message_begin_internet_message_headers (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "internetMessageHeaders");
+}
+
+void
+e_m365_mail_message_end_internet_message_headers (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+const gchar *
+e_m365_mail_message_get_internet_message_id (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "internetMessageId", NULL);
+}
+
+void
+e_m365_mail_message_add_internet_message_id (JsonBuilder *builder,
+ const gchar *message_id)
+{
+ e_m365_json_add_nonempty_string_member (builder, "internetMessageId", message_id);
+}
+
+gboolean
+e_m365_mail_message_get_is_delivery_receipt_requested (EM365MailMessage *mail)
+{
+ return e_m365_json_get_boolean_member (mail, "isDeliveryReceiptRequested", FALSE);
+}
+
+void
+e_m365_mail_message_add_is_delivery_receipt_requested (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isDeliveryReceiptRequested", value);
+}
+
+gboolean
+e_m365_mail_message_get_is_draft (EM365MailMessage *mail)
+{
+ return e_m365_json_get_boolean_member (mail, "isDraft", FALSE);
+}
+
+gboolean
+e_m365_mail_message_get_is_read (EM365MailMessage *mail)
+{
+ return e_m365_json_get_boolean_member (mail, "isRead", FALSE);
+}
+
+void
+e_m365_mail_message_add_is_read (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isRead", value);
+}
+
+gboolean
+e_m365_mail_message_get_is_read_receipt_requested (EM365MailMessage *mail)
+{
+ return e_m365_json_get_boolean_member (mail, "isReadReceiptRequested", FALSE);
+}
+
+void
+e_m365_mail_message_add_is_read_receipt_requested (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isReadReceiptRequested", value);
+}
+
+time_t
+e_m365_mail_message_get_last_modified_date_time (EM365MailMessage *mail)
+{
+ return e_m365_get_date_time_offset_member (mail, "lastModifiedDateTime");
+}
+
+const gchar *
+e_m365_mail_message_get_parent_folder_id (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "parentFolderId", NULL);
+}
+
+time_t
+e_m365_mail_message_get_received_date_time (EM365MailMessage *mail)
+{
+ return e_m365_get_date_time_offset_member (mail, "receivedDateTime");
+}
+
+void
+e_m365_mail_message_add_received_date_time (JsonBuilder *builder,
+ time_t value)
+{
+ e_m365_add_date_time_offset_member (builder, "receivedDateTime", value);
+}
+
+JsonArray * /* EM365Recipient * */
+e_m365_mail_message_get_reply_to (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "replyTo");
+}
+
+void
+e_m365_mail_message_begin_reply_to (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "replyTo");
+}
+
+void
+e_m365_mail_message_end_reply_to (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+EM365Recipient *
+e_m365_mail_message_get_sender (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "sender");
+}
+
+void
+e_m365_mail_message_add_sender (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address)
+{
+ g_return_if_fail ((name && *name) || (address && *address));
+
+ e_m365_add_recipient (builder, "sender", name, address);
+}
+
+time_t
+e_m365_mail_message_get_sent_date_time (EM365MailMessage *mail)
+{
+ return e_m365_get_date_time_offset_member (mail, "sentDateTime");
+}
+
+void
+e_m365_mail_message_add_sent_date_time (JsonBuilder *builder,
+ time_t value)
+{
+ e_m365_add_date_time_offset_member (builder, "sentDateTime", value);
+}
+
+const gchar *
+e_m365_mail_message_get_subject (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "subject", NULL);
+}
+
+void
+e_m365_mail_message_add_subject (JsonBuilder *builder,
+ const gchar *subject)
+{
+ e_m365_json_add_nonempty_string_member (builder, "subject", subject);
+}
+
+JsonArray * /* EM365Recipient * */
+e_m365_mail_message_get_to_recipients (EM365MailMessage *mail)
+{
+ return e_m365_json_get_array_member (mail, "toRecipients");
+}
+
+void
+e_m365_mail_message_begin_to_recipients (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "toRecipients");
+}
+
+void
+e_m365_mail_message_end_to_recipients (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+EM365ItemBody *
+e_m365_mail_message_get_unique_body (EM365MailMessage *mail)
+{
+ return e_m365_json_get_object_member (mail, "uniqueBody");
+}
+
+const gchar *
+e_m365_mail_message_get_web_link (EM365MailMessage *mail)
+{
+ return e_m365_json_get_string_member (mail, "webLink", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/attachment?view=graph-rest-1.0 */
+
+EM365AttachmentDataType
+e_m365_attachment_get_data_type (EM365Attachment *attachment)
+{
+ return m365_json_utils_get_json_as_enum (attachment, "@odata.type",
+ attachment_data_type_map, G_N_ELEMENTS (attachment_data_type_map),
+ E_M365_ATTACHMENT_DATA_TYPE_NOT_SET,
+ E_M365_ATTACHMENT_DATA_TYPE_UNKNOWN);
+}
+
+void
+e_m365_attachment_begin_attachment (JsonBuilder *builder,
+ EM365AttachmentDataType data_type)
+{
+ e_m365_json_begin_object_member (builder, NULL);
+
+ m365_json_utils_add_enum_as_json (builder, "@odata.type", data_type,
+ attachment_data_type_map, G_N_ELEMENTS (attachment_data_type_map),
+ E_M365_ATTACHMENT_DATA_TYPE_NOT_SET,
+ E_M365_ATTACHMENT_DATA_TYPE_FILE);
+}
+
+void
+e_m365_attachment_end_attachment (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+const gchar *
+e_m365_attachment_get_content_type (EM365Attachment *attachment)
+{
+ return e_m365_json_get_string_member (attachment, "contentType", NULL);
+}
+
+void
+e_m365_attachment_add_content_type (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "contentType", value);
+}
+
+const gchar *
+e_m365_attachment_get_id (EM365Attachment *attachment)
+{
+ return e_m365_json_get_string_member (attachment, "id", NULL);
+}
+
+gboolean
+e_m365_attachment_get_is_inline (EM365Attachment *attachment)
+{
+ return e_m365_json_get_boolean_member (attachment, "isInline", FALSE);
+}
+
+void
+e_m365_attachment_add_is_inline (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isInline", value);
+}
+
+time_t
+e_m365_attachment_get_last_modified_date_time (EM365Attachment *attachment)
+{
+ return e_m365_get_date_time_offset_member (attachment, "lastModifiedDateTime");
+}
+
+void
+e_m365_attachment_add_last_modified_date_time (JsonBuilder *builder,
+ time_t value)
+{
+ e_m365_add_date_time_offset_member (builder, "lastModifiedDateTime", value);
+}
+
+const gchar *
+e_m365_attachment_get_name (EM365Attachment *attachment)
+{
+ return e_m365_json_get_string_member (attachment, "name", NULL);
+}
+
+void
+e_m365_attachment_add_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "name", value);
+}
+
+gint32
+e_m365_attachment_get_size (EM365Attachment *attachment)
+{
+ return (gint32) e_m365_json_get_int_member (attachment, "size", -1);
+}
+
+void
+e_m365_attachment_add_size (JsonBuilder *builder,
+ gint32 value)
+{
+ e_m365_json_add_int_member (builder, "size", value);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/fileattachment?view=graph-rest-1.0 */
+
+const gchar * /* base64-encoded */
+e_m365_file_attachment_get_content_bytes (EM365Attachment *attachment)
+{
+ return e_m365_json_get_string_member (attachment, "contentBytes", NULL);
+}
+
+void
+e_m365_file_attachment_add_content_bytes (JsonBuilder *builder,
+ const gchar *base64_value)
+{
+ e_m365_json_add_string_member (builder, "contentBytes", base64_value);
+}
+
+const gchar *
+e_m365_file_attachment_get_content_id (EM365Attachment *attachment)
+{
+ return e_m365_json_get_string_member (attachment, "contentId", NULL);
+}
+
+void
+e_m365_file_attachment_add_content_id (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "contentId", value);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/emailaddress?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_email_address_get_name (EM365EmailAddress *email)
+{
+ return e_m365_json_get_string_member (email, "name", NULL);
+}
+
+const gchar *
+e_m365_email_address_get_address (EM365EmailAddress *email)
+{
+ return e_m365_json_get_string_member (email, "address", NULL);
+}
+
+void
+e_m365_add_email_address (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *name,
+ const gchar *address)
+{
+ g_return_if_fail ((name && *name) || (address && *address));
+
+ e_m365_json_begin_object_member (builder, member_name);
+
+ e_m365_json_add_nonempty_string_member (builder, "name", name);
+ e_m365_json_add_nonempty_string_member (builder, "address", address);
+
+ e_m365_json_end_object_member (builder); /* member_name */
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/physicaladdress?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_physical_address_get_city (EM365PhysicalAddress *address)
+{
+ return e_m365_json_get_string_member (address, "city", NULL);
+}
+
+const gchar *
+e_m365_physical_address_get_country_or_region (EM365PhysicalAddress *address)
+{
+ return e_m365_json_get_string_member (address, "countryOrRegion", NULL);
+}
+
+const gchar *
+e_m365_physical_address_get_postal_code (EM365PhysicalAddress *address)
+{
+ return e_m365_json_get_string_member (address, "postalCode", NULL);
+}
+
+const gchar *
+e_m365_physical_address_get_state (EM365PhysicalAddress *address)
+{
+ return e_m365_json_get_string_member (address, "state", NULL);
+}
+
+const gchar *
+e_m365_physical_address_get_street (EM365PhysicalAddress *address)
+{
+ return e_m365_json_get_string_member (address, "street", NULL);
+}
+
+void
+e_m365_add_physical_address (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street)
+{
+ if ((city && *city) ||
+ (country_or_region && *country_or_region) ||
+ (postal_code && *postal_code) ||
+ (state && *state) ||
+ (street && *street)) {
+ e_m365_json_begin_object_member (builder, member_name);
+ e_m365_json_add_nonempty_string_member (builder, "city", city);
+ e_m365_json_add_nonempty_string_member (builder, "countryOrRegion", country_or_region);
+ e_m365_json_add_nonempty_string_member (builder, "postalCode", postal_code);
+ e_m365_json_add_nonempty_string_member (builder, "state", state);
+ e_m365_json_add_nonempty_string_member (builder, "street", street);
+ e_m365_json_end_object_member (builder);
+ } else {
+ e_m365_json_begin_object_member (builder, member_name);
+ e_m365_json_end_object_member (builder);
+ }
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_contact_get_id (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "id", NULL);
+}
+
+const gchar *
+e_m365_contact_get_parent_folder_id (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "parentFolderId", NULL);
+}
+
+const gchar *
+e_m365_contact_get_change_key (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "changeKey", NULL);
+}
+
+time_t
+e_m365_contact_get_created_date_time (EM365Contact *contact)
+{
+ return e_m365_get_date_time_offset_member (contact, "createdDateTime");
+}
+
+time_t
+e_m365_contact_get_last_modified_date_time (EM365Contact *contact)
+{
+ return e_m365_get_date_time_offset_member (contact, "lastModifiedDateTime");
+}
+
+const gchar *
+e_m365_contact_get_assistant_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "assistantName", NULL);
+}
+
+void
+e_m365_contact_add_assistant_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "assistantName", value);
+}
+
+time_t
+e_m365_contact_get_birthday (EM365Contact *contact)
+{
+ return e_m365_get_date_time_offset_member (contact, "birthday");
+}
+
+void
+e_m365_contact_add_birthday (JsonBuilder *builder,
+ time_t value)
+{
+ e_m365_add_date_time_offset_member (builder, "birthday", value);
+}
+
+EM365PhysicalAddress *
+e_m365_contact_get_business_address (EM365Contact *contact)
+{
+ return e_m365_json_get_object_member (contact, "businessAddress");
+}
+
+void
+e_m365_contact_add_business_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street)
+{
+ e_m365_add_physical_address (builder, "businessAddress", city, country_or_region, postal_code, state,
street);
+}
+
+const gchar *
+e_m365_contact_get_business_home_page (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "businessHomePage", NULL);
+}
+
+void
+e_m365_contact_add_business_home_page (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "businessHomePage", value);
+}
+
+JsonArray * /* const gchar * */
+e_m365_contact_get_business_phones (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "businessPhones");
+}
+
+void
+e_m365_contact_begin_business_phones (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "businessPhones");
+}
+
+void
+e_m365_contact_end_business_phones (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_contact_add_business_phone (JsonBuilder *builder,
+ const gchar *value)
+{
+ g_return_if_fail (value && *value);
+
+ json_builder_add_string_value (builder, value);
+}
+
+JsonArray * /* const gchar * */
+e_m365_contact_get_categories (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "categories");
+}
+
+void
+e_m365_contact_begin_categories (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_m365_contact_end_categories (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_contact_add_category (JsonBuilder *builder,
+ const gchar *category)
+{
+ g_return_if_fail (category && *category);
+
+ json_builder_add_string_value (builder, category);
+}
+
+JsonArray * /* const gchar * */
+e_m365_contact_get_children (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "children");
+}
+
+void
+e_m365_contact_begin_children (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "children");
+}
+
+void
+e_m365_contact_end_children (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_contact_add_child (JsonBuilder *builder,
+ const gchar *value)
+{
+ g_return_if_fail (value && *value);
+
+ json_builder_add_string_value (builder, value);
+}
+
+const gchar *
+e_m365_contact_get_company_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "companyName", NULL);
+}
+
+void
+e_m365_contact_add_company_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "companyName", value);
+}
+
+const gchar *
+e_m365_contact_get_department (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "department", NULL);
+}
+
+void
+e_m365_contact_add_department (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "department", value);
+}
+
+const gchar *
+e_m365_contact_get_display_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "displayName", NULL);
+}
+
+void
+e_m365_contact_add_display_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "displayName", value);
+}
+
+JsonArray * /* EM365EmailAddress * */
+e_m365_contact_get_email_addresses (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "emailAddresses");
+}
+
+void
+e_m365_contact_begin_email_addresses (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "emailAddresses");
+}
+
+void
+e_m365_contact_end_email_addresses (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+const gchar *
+e_m365_contact_get_file_as (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "fileAs", NULL);
+}
+
+void
+e_m365_contact_add_file_as (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "fileAs", value);
+}
+
+const gchar *
+e_m365_contact_get_generation (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "generation", NULL);
+}
+
+void
+e_m365_contact_add_generation (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "generation", value);
+}
+
+const gchar *
+e_m365_contact_get_given_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "givenName", NULL);
+}
+
+void
+e_m365_contact_add_given_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "givenName", value);
+}
+
+EM365PhysicalAddress *
+e_m365_contact_get_home_address (EM365Contact *contact)
+{
+ return e_m365_json_get_object_member (contact, "homeAddress");
+}
+
+void
+e_m365_contact_add_home_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street)
+{
+ e_m365_add_physical_address (builder, "homeAddress", city, country_or_region, postal_code, state,
street);
+}
+
+JsonArray * /* const gchar * */
+e_m365_contact_get_home_phones (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "homePhones");
+}
+
+void
+e_m365_contact_begin_home_phones (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "homePhones");
+}
+
+void
+e_m365_contact_end_home_phones (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_contact_add_home_phone (JsonBuilder *builder,
+ const gchar *value)
+{
+ g_return_if_fail (value && *value);
+
+ json_builder_add_string_value (builder, value);
+}
+
+JsonArray * /* const gchar * */
+e_m365_contact_get_im_addresses (EM365Contact *contact)
+{
+ return e_m365_json_get_array_member (contact, "imAddresses");
+}
+
+void
+e_m365_contact_begin_im_addresses (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "imAddresses");
+}
+
+void
+e_m365_contact_end_im_addresses (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_contact_add_im_address (JsonBuilder *builder,
+ const gchar *value)
+{
+ g_return_if_fail (value && *value);
+
+ json_builder_add_string_value (builder, value);
+}
+
+const gchar *
+e_m365_contact_get_initials (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "initials", NULL);
+}
+
+void
+e_m365_contact_add_initials (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "initials", value);
+}
+
+const gchar *
+e_m365_contact_get_job_title (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "jobTitle", NULL);
+}
+
+void
+e_m365_contact_add_job_title (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "jobTitle", value);
+}
+
+const gchar *
+e_m365_contact_get_manager (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "manager", NULL);
+}
+
+void
+e_m365_contact_add_manager (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "manager", value);
+}
+
+const gchar *
+e_m365_contact_get_middle_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "middleName", NULL);
+}
+
+void
+e_m365_contact_add_middle_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "middleName", value);
+}
+
+const gchar *
+e_m365_contact_get_mobile_phone (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "mobilePhone", NULL);
+}
+
+void
+e_m365_contact_add_mobile_phone (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "mobilePhone", value);
+}
+
+const gchar *
+e_m365_contact_get_nick_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "nickName", NULL);
+}
+
+void
+e_m365_contact_add_nick_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "nickName", value);
+}
+
+const gchar *
+e_m365_contact_get_office_location (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "officeLocation", NULL);
+}
+
+void
+e_m365_contact_add_office_location (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "officeLocation", value);
+}
+
+EM365PhysicalAddress *
+e_m365_contact_get_other_address (EM365Contact *contact)
+{
+ return e_m365_json_get_object_member (contact, "otherAddress");
+}
+
+void
+e_m365_contact_add_other_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street)
+{
+ e_m365_add_physical_address (builder, "otherAddress", city, country_or_region, postal_code, state,
street);
+}
+
+const gchar *
+e_m365_contact_get_personal_notes (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "personalNotes", NULL);
+}
+
+void
+e_m365_contact_add_personal_notes (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "personalNotes", value);
+}
+
+const gchar *
+e_m365_contact_get_profession (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "profession", NULL);
+}
+
+void
+e_m365_contact_add_profession (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "profession", value);
+}
+
+const gchar *
+e_m365_contact_get_spouse_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "spouseName", NULL);
+}
+
+void
+e_m365_contact_add_spouse_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "spouseName", value);
+}
+
+const gchar *
+e_m365_contact_get_surname (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "surname", NULL);
+}
+
+void
+e_m365_contact_add_surname (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "surname", value);
+}
+
+const gchar *
+e_m365_contact_get_title (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "title", NULL);
+}
+
+void
+e_m365_contact_add_title (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "title", value);
+}
+
+const gchar *
+e_m365_contact_get_yomi_company_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "yomiCompanyName", NULL);
+}
+
+void
+e_m365_contact_add_yomi_company_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "yomiCompanyName", value);
+}
+
+const gchar *
+e_m365_contact_get_yomi_given_name (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "yomiGivenName", NULL);
+}
+
+void
+e_m365_contact_add_yomi_given_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "yomiGivenName", value);
+}
+
+const gchar *
+e_m365_contact_get_yomi_surname (EM365Contact *contact)
+{
+ return e_m365_json_get_string_member (contact, "yomiSurname", NULL);
+}
+
+void
+e_m365_contact_add_yomi_surname (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "yomiSurname", value);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendargroup?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_calendar_group_get_id (EM365CalendarGroup *group)
+{
+ return e_m365_json_get_string_member (group, "id", NULL);
+}
+
+const gchar *
+e_m365_calendar_group_get_change_key (EM365CalendarGroup *group)
+{
+ return e_m365_json_get_string_member (group, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_calendar_group_get_class_id (EM365CalendarGroup *group)
+{
+ return e_m365_json_get_string_member (group, "classId", NULL);
+}
+
+const gchar *
+e_m365_calendar_group_get_name (EM365CalendarGroup *group)
+{
+ return e_m365_json_get_string_member (group, "name", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_calendar_get_id (EM365Calendar *calendar)
+{
+ return e_m365_json_get_string_member (calendar, "id", NULL);
+}
+
+const gchar *
+e_m365_calendar_get_change_key (EM365Calendar *calendar)
+{
+ return e_m365_json_get_string_member (calendar, "changeKey", NULL);
+}
+
+gboolean
+e_m365_calendar_get_can_edit (EM365Calendar *calendar)
+{
+ return e_m365_json_get_boolean_member (calendar, "canEdit", FALSE);
+}
+
+gboolean
+e_m365_calendar_get_can_share (EM365Calendar *calendar)
+{
+ return e_m365_json_get_boolean_member (calendar, "canShare", FALSE);
+}
+
+gboolean
+e_m365_calendar_get_can_view_private_items (EM365Calendar *calendar)
+{
+ return e_m365_json_get_boolean_member (calendar, "canViewPrivateItems", FALSE);
+}
+
+gboolean
+e_m365_calendar_get_is_removable (EM365Calendar *calendar)
+{
+ return e_m365_json_get_boolean_member (calendar, "isRemovable", FALSE);
+}
+
+gboolean
+e_m365_calendar_get_is_tallying_responses (EM365Calendar *calendar)
+{
+ return e_m365_json_get_boolean_member (calendar, "isTallyingResponses", FALSE);
+}
+
+EM365EmailAddress *
+e_m365_calendar_get_owner (EM365Calendar *calendar)
+{
+ return e_m365_json_get_object_member (calendar, "owner");
+}
+
+const gchar *
+e_m365_calendar_get_name (EM365Calendar *calendar)
+{
+ return e_m365_json_get_string_member (calendar, "name", NULL);
+}
+
+void
+e_m365_calendar_add_name (JsonBuilder *builder,
+ const gchar *name)
+{
+ e_m365_json_add_nonempty_string_member (builder, "name", name);
+}
+
+guint32 /* bit-or of EM365OnlineMeetingProviderType */
+e_m365_calendar_get_allowed_online_meeting_providers (EM365Calendar *calendar)
+{
+ guint32 providers = E_M365_ONLINE_MEETING_PROVIDER_NOT_SET;
+ JsonArray *array;
+
+ array = e_m365_json_get_array_member (calendar, "allowedOnlineMeetingProviders");
+
+ if (array) {
+ guint ii, len;
+
+ providers = E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN;
+
+ len = json_array_get_length (array);
+
+ for (ii = 0; ii < len; ii++) {
+ const gchar *str = json_array_get_string_element (array, ii);
+ gint enum_value;
+
+ if (!str)
+ continue;
+
+ enum_value = m365_json_utils_json_value_as_enum (str,
+ meeting_provider_map, G_N_ELEMENTS (meeting_provider_map),
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN);
+
+ if (enum_value != E_M365_ONLINE_MEETING_PROVIDER_NOT_SET)
+ providers |= enum_value;
+ }
+ }
+
+ return providers;
+}
+
+void
+e_m365_calendar_add_allowed_online_meeting_providers (JsonBuilder *builder,
+ guint providers) /* bit-or of
EM365OnlineMeetingProviderType */
+{
+ gint ii;
+
+ if (providers == E_M365_ONLINE_MEETING_PROVIDER_NOT_SET)
+ return;
+
+ e_m365_json_begin_array_member (builder, "allowedOnlineMeetingProviders");
+
+ if (providers == E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN)
+ json_builder_add_string_value (builder, "unknown");
+
+ for (ii = 0; ii < G_N_ELEMENTS (meeting_provider_map); ii++) {
+ if ((providers & meeting_provider_map[ii].enum_value) != 0)
+ json_builder_add_string_value (builder, meeting_provider_map[ii].json_value);
+ }
+
+ e_m365_json_end_array_member (builder);
+}
+
+EM365CalendarColorType
+e_m365_calendar_get_color (EM365Calendar *calendar)
+{
+ const gchar *color;
+ gint ii;
+
+ color = e_m365_json_get_string_member (calendar, "color", NULL);
+
+ if (!color)
+ return E_M365_CALENDAR_COLOR_NOT_SET;
+
+ for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+ if (g_ascii_strcasecmp (color_map[ii].name, color) == 0)
+ return color_map[ii].value;
+ }
+
+ return E_M365_CALENDAR_COLOR_UNKNOWN;
+}
+
+void
+e_m365_calendar_add_color (JsonBuilder *builder,
+ EM365CalendarColorType color)
+{
+ const gchar *name = NULL;
+ gint ii;
+
+ for (ii = 0; ii < G_N_ELEMENTS (color_map); ii++) {
+ if (color_map[ii].value == color) {
+ name = color_map[ii].name;
+ break;
+ }
+ }
+
+ if (name && g_ascii_strcasecmp (name, "maxColor") != 0)
+ e_m365_json_add_string_member (builder, "color", name);
+}
+
+EM365OnlineMeetingProviderType
+e_m365_calendar_get_default_online_meeting_provider (EM365Calendar *calendar)
+{
+ return m365_json_utils_get_json_as_enum (calendar, "defaultOnlineMeetingProvider",
+ meeting_provider_map, G_N_ELEMENTS (meeting_provider_map),
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN);
+}
+
+void
+e_m365_calendar_add_default_online_meeting_provider (JsonBuilder *builder,
+ EM365OnlineMeetingProviderType provider)
+{
+ m365_json_utils_add_enum_as_json (builder, "defaultOnlineMeetingProvider", provider,
+ meeting_provider_map, G_N_ELEMENTS (meeting_provider_map),
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/responsestatus?view=graph-rest-1.0 */
+
+EM365ResponseType
+e_m365_response_status_get_response (EM365ResponseStatus *response_status)
+{
+ return m365_json_utils_get_json_as_enum (response_status, "response",
+ response_map, G_N_ELEMENTS (response_map),
+ E_M365_RESPONSE_NOT_SET,
+ E_M365_RESPONSE_UNKNOWN);
+}
+
+time_t
+e_m365_response_status_get_time (EM365ResponseStatus *response_status)
+{
+ return e_m365_get_date_time_offset_member (response_status, "time");
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/attendee?view=graph-rest-1.0 */
+
+EM365ResponseStatus *
+e_m365_attendee_get_status (EM365Attendee *attendee)
+{
+ return e_m365_json_get_object_member (attendee, "status");
+}
+
+EM365AttendeeType
+e_m365_attendee_get_type (EM365Attendee *attendee)
+{
+ return m365_json_utils_get_json_as_enum (attendee, "type",
+ attendee_map, G_N_ELEMENTS (attendee_map),
+ E_M365_ATTENDEE_NOT_SET,
+ E_M365_ATTENDEE_UNKNOWN);
+}
+
+EM365EmailAddress *
+e_m365_attendee_get_email_address (EM365Attendee *attendee)
+{
+ return e_m365_json_get_object_member (attendee, "emailAddress");
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlookgeocoordinates?view=graph-rest-1.0 */
+
+gdouble
+e_m365_outlook_geo_coordinates_get_accuracy (EM365OutlookGeoCoordinates *coords)
+{
+ return e_m365_json_get_double_member (coords, "accuracy", 0.0);
+}
+
+void
+e_m365_outlook_geo_coordinates_add_accuracy (JsonBuilder *builder,
+ gdouble value)
+{
+ e_m365_json_add_double_member (builder, "accuracy", value);
+}
+
+gdouble
+e_m365_outlook_geo_coordinates_get_altitude (EM365OutlookGeoCoordinates *coords)
+{
+ return e_m365_json_get_double_member (coords, "altitude", 0.0);
+}
+
+void
+e_m365_outlook_geo_coordinates_add_altitude (JsonBuilder *builder,
+ gdouble value)
+{
+ e_m365_json_add_double_member (builder, "altitude", value);
+}
+
+gdouble
+e_m365_outlook_geo_coordinates_get_altitude_accuracy (EM365OutlookGeoCoordinates *coords)
+{
+ return e_m365_json_get_double_member (coords, "altitudeAccuracy", 0.0);
+}
+
+void
+e_m365_outlook_geo_coordinates_add_altitude_accuracy (JsonBuilder *builder,
+ gdouble value)
+{
+ e_m365_json_add_double_member (builder, "altitudeAccuracy", value);
+}
+
+gdouble
+e_m365_outlook_geo_coordinates_get_latitude (EM365OutlookGeoCoordinates *coords)
+{
+ return e_m365_json_get_double_member (coords, "latitude", 0.0);
+}
+
+void
+e_m365_outlook_geo_coordinates_add_latitude (JsonBuilder *builder,
+ gdouble value)
+{
+ e_m365_json_add_double_member (builder, "latitude", value);
+}
+
+gdouble
+e_m365_outlook_geo_coordinates_get_longitude (EM365OutlookGeoCoordinates *coords)
+{
+ return e_m365_json_get_double_member (coords, "longitude", 0.0);
+}
+
+void
+e_m365_outlook_geo_coordinates_add_longitude (JsonBuilder *builder,
+ gdouble value)
+{
+ e_m365_json_add_double_member (builder, "longitude", value);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/location?view=graph-rest-1.0 */
+
+EM365PhysicalAddress *
+e_m365_location_get_address (EM365Location *location)
+{
+ return e_m365_json_get_object_member (location, "address");
+}
+
+void
+e_m365_location_add_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street)
+{
+ e_m365_add_physical_address (builder, "address", city, country_or_region, postal_code, state, street);
+}
+
+EM365OutlookGeoCoordinates *
+e_m365_location_get_coordinates (EM365Location *location)
+{
+ return e_m365_json_get_object_member (location, "coordinates");
+}
+
+void
+e_m365_location_begin_coordinates (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "coordinates");
+}
+
+void
+e_m365_location_end_coordinates (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+const gchar *
+e_m365_location_get_display_name (EM365Location *location)
+{
+ return e_m365_json_get_string_member (location, "displayName", NULL);
+}
+
+void
+e_m365_location_add_display_name (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "displayName", value);
+}
+
+const gchar *
+e_m365_location_get_email_address (EM365Location *location)
+{
+ return e_m365_json_get_string_member (location, "locationEmailAddress", NULL);
+}
+
+void
+e_m365_location_add_email_address (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "locationEmailAddress", value);
+}
+
+const gchar *
+e_m365_location_get_uri (EM365Location *location)
+{
+ return e_m365_json_get_string_member (location, "locationUri", NULL);
+}
+
+void
+e_m365_location_add_uri (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_nonempty_or_null_string_member (builder, "locationUri", value);
+}
+
+EM365LocationType
+e_m365_location_get_type (EM365Location *location)
+{
+ return m365_json_utils_get_json_as_enum (location, "locationType",
+ location_type_map, G_N_ELEMENTS (location_type_map),
+ E_M365_LOCATION_NOT_SET,
+ E_M365_LOCATION_UNKNOWN);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/phone?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_phone_get_number (EM365Phone *phone)
+{
+ return e_m365_json_get_string_member (phone, "number", NULL);
+}
+
+EM365PhoneType
+e_m365_phone_get_type (EM365Phone *phone)
+{
+ return m365_json_utils_get_json_as_enum (phone, "type",
+ phone_map, G_N_ELEMENTS (phone_map),
+ E_M365_PHONE_NOT_SET,
+ E_M365_PHONE_UNKNOWN);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/onlinemeetinginfo?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_online_meeting_info_get_conference_id (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_string_member (meeting_info, "conferenceId", NULL);
+}
+
+const gchar *
+e_m365_online_meeting_info_get_join_url (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_string_member (meeting_info, "joinUrl", NULL);
+}
+
+JsonArray * /* EM365Phone * */
+e_m365_online_meeting_info_get_phones (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_array_member (meeting_info, "phones");
+}
+
+const gchar *
+e_m365_online_meeting_info_get_quick_dial (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_string_member (meeting_info, "quickDial", NULL);
+}
+
+JsonArray * /* gchar * */
+e_m365_online_meeting_info_get_toll_free_numbers (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_array_member (meeting_info, "tollFreeNumbers");
+}
+
+const gchar *
+e_m365_online_meeting_info_get_toll_number (EM365OnlineMeetingInfo *meeting_info)
+{
+ return e_m365_json_get_string_member (meeting_info, "tollNumber", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/patternedrecurrence?view=graph-rest-1.0 */
+
+EM365RecurrencePattern *
+e_m365_patterned_recurrence_get_pattern (EM365PatternedRecurrence *patterned_recurrence)
+{
+ return e_m365_json_get_object_member (patterned_recurrence, "pattern");
+}
+
+void
+e_m365_patterned_recurrence_begin_pattern (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "pattern");
+}
+
+void
+e_m365_patterned_recurrence_end_pattern (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+EM365RecurrenceRange *
+e_m365_patterned_recurrence_get_range (EM365PatternedRecurrence *patterned_recurrence)
+{
+ return e_m365_json_get_object_member (patterned_recurrence, "range");
+}
+
+void
+e_m365_patterned_recurrence_begin_range (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "range");
+}
+
+void
+e_m365_patterned_recurrence_end_range (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/recurrencepattern?view=graph-rest-1.0 */
+
+EM365DayOfWeekType
+e_m365_array_get_day_of_week_element (JsonArray *array, /* const gchar * representing EM365DayOfWeekType */
+ guint index)
+{
+ return m365_json_utils_json_value_as_enum (json_array_get_string_element (array, index),
+ day_of_week_map, G_N_ELEMENTS (day_of_week_map),
+ E_M365_DAY_OF_WEEK_NOT_SET,
+ E_M365_DAY_OF_WEEK_UNKNOWN);
+}
+
+gint
+e_m365_recurrence_pattern_get_day_of_month (EM365RecurrencePattern *pattern)
+{
+ return e_m365_json_get_int_member (pattern, "dayOfMonth", 0);
+}
+
+void
+e_m365_recurrence_pattern_add_day_of_month (JsonBuilder *builder,
+ gint value)
+{
+ e_m365_json_add_int_member (builder, "dayOfMonth", value);
+}
+
+JsonArray * /* const gchar * representing EM365DayOfWeekType, use e_m365_array_get_day_of_week_element() */
+e_m365_recurrence_pattern_get_days_of_week (EM365RecurrencePattern *pattern)
+{
+ return e_m365_json_get_array_member (pattern, "daysOfWeek");
+}
+
+void
+e_m365_recurrence_pattern_begin_days_of_week (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "daysOfWeek");
+}
+
+void
+e_m365_recurrence_pattern_end_days_of_week (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_recurrence_pattern_add_day_of_week (JsonBuilder *builder,
+ EM365DayOfWeekType value)
+{
+ m365_json_utils_add_enum_as_json (builder, NULL, value,
+ day_of_week_map, G_N_ELEMENTS (day_of_week_map),
+ E_M365_DAY_OF_WEEK_NOT_SET,
+ E_M365_DAY_OF_WEEK_NOT_SET);
+}
+
+EM365DayOfWeekType
+e_m365_recurrence_pattern_get_first_day_of_week (EM365RecurrencePattern *pattern)
+{
+ return m365_json_utils_get_json_as_enum (pattern, "firstDayOfWeek",
+ day_of_week_map, G_N_ELEMENTS (day_of_week_map),
+ E_M365_DAY_OF_WEEK_NOT_SET,
+ E_M365_DAY_OF_WEEK_UNKNOWN);
+}
+
+void
+e_m365_recurrence_pattern_add_first_day_of_week (JsonBuilder *builder,
+ EM365DayOfWeekType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "firstDayOfWeek", value,
+ day_of_week_map, G_N_ELEMENTS (day_of_week_map),
+ E_M365_DAY_OF_WEEK_NOT_SET,
+ E_M365_DAY_OF_WEEK_NOT_SET);
+}
+
+EM365WeekIndexType
+e_m365_recurrence_pattern_get_index (EM365RecurrencePattern *pattern)
+{
+ return m365_json_utils_get_json_as_enum (pattern, "index",
+ week_index_map, G_N_ELEMENTS (week_index_map),
+ E_M365_WEEK_INDEX_NOT_SET,
+ E_M365_WEEK_INDEX_UNKNOWN);
+}
+
+void
+e_m365_recurrence_pattern_add_index (JsonBuilder *builder,
+ EM365WeekIndexType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "index", value,
+ week_index_map, G_N_ELEMENTS (week_index_map),
+ E_M365_WEEK_INDEX_NOT_SET,
+ E_M365_WEEK_INDEX_NOT_SET);
+}
+
+gint
+e_m365_recurrence_pattern_get_interval (EM365RecurrencePattern *pattern)
+{
+ return e_m365_json_get_int_member (pattern, "interval", -1);
+}
+
+void
+e_m365_recurrence_pattern_add_interval (JsonBuilder *builder,
+ gint value)
+{
+ e_m365_json_add_int_member (builder, "interval", value);
+}
+
+gint
+e_m365_recurrence_pattern_get_month (EM365RecurrencePattern *pattern)
+{
+ return e_m365_json_get_int_member (pattern, "month", -1);
+}
+
+void
+e_m365_recurrence_pattern_add_month (JsonBuilder *builder,
+ gint value)
+{
+ e_m365_json_add_int_member (builder, "month", value);
+}
+
+EM365RecurrencePatternType
+e_m365_recurrence_pattern_get_type (EM365RecurrencePattern *pattern)
+{
+ return m365_json_utils_get_json_as_enum (pattern, "type",
+ recurrence_pattern_map, G_N_ELEMENTS (recurrence_pattern_map),
+ E_M365_RECURRENCE_PATTERN_NOT_SET,
+ E_M365_RECURRENCE_PATTERN_UNKNOWN);
+}
+
+void
+e_m365_recurrence_pattern_add_type (JsonBuilder *builder,
+ EM365RecurrencePatternType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "type", value,
+ recurrence_pattern_map, G_N_ELEMENTS (recurrence_pattern_map),
+ E_M365_RECURRENCE_PATTERN_NOT_SET,
+ E_M365_RECURRENCE_PATTERN_UNKNOWN);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/recurrencerange?view=graph-rest-1.0 */
+
+EM365Date
+e_m365_recurrence_range_get_end_date (EM365RecurrenceRange *range)
+{
+ return e_m365_date_get (range, "endDate");
+}
+
+void
+e_m365_recurrence_range_add_end_date (JsonBuilder *builder,
+ EM365Date value)
+{
+ e_m365_add_date (builder, "endDate", value);
+}
+
+gint
+e_m365_recurrence_range_get_number_of_occurrences (EM365RecurrenceRange *range)
+{
+ return e_m365_json_get_int_member (range, "numberOfOccurrences", -1);
+}
+
+void
+e_m365_recurrence_range_add_number_of_occurrences (JsonBuilder *builder,
+ gint value)
+{
+ e_m365_json_add_int_member (builder, "numberOfOccurrences", value);
+}
+
+const gchar *
+e_m365_recurrence_range_get_recurrence_time_zone (EM365RecurrenceRange *range)
+{
+ return e_m365_json_get_string_member (range, "recurrenceTimeZone", NULL);
+}
+
+void
+e_m365_recurrence_range_add_recurrence_time_zone (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "recurrenceTimeZone", value);
+}
+
+EM365Date
+e_m365_recurrence_range_get_start_date (EM365RecurrenceRange *range)
+{
+ return e_m365_date_get (range, "startDate");
+}
+
+void
+e_m365_recurrence_range_add_start_date (JsonBuilder *builder,
+ EM365Date value)
+{
+ e_m365_add_date (builder, "startDate", value);
+}
+
+EM365RecurrenceRangeType
+e_m365_recurrence_range_get_type (EM365RecurrenceRange *range)
+{
+ return m365_json_utils_get_json_as_enum (range, "type",
+ recurrence_range_map, G_N_ELEMENTS (recurrence_range_map),
+ E_M365_RECURRENCE_RANGE_NOT_SET,
+ E_M365_RECURRENCE_RANGE_UNKNOWN);
+}
+
+void
+e_m365_recurrence_range_add_type (JsonBuilder *builder,
+ EM365RecurrenceRangeType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "type", value,
+ recurrence_range_map, G_N_ELEMENTS (recurrence_range_map),
+ E_M365_RECURRENCE_RANGE_NOT_SET,
+ E_M365_RECURRENCE_RANGE_UNKNOWN);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_event_get_id (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "id", NULL);
+}
+
+const gchar *
+e_m365_event_get_change_key (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "changeKey", NULL);
+}
+
+JsonArray * /* EM365Attendee * */
+e_m365_event_get_attendees (EM365Event *event)
+{
+ return e_m365_json_get_array_member (event, "attendees");
+}
+
+void
+e_m365_event_begin_attendees (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "attendees");
+}
+
+void
+e_m365_event_end_attendees (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_event_add_attendee (JsonBuilder *builder,
+ EM365AttendeeType type,
+ EM365ResponseType response,
+ time_t response_time,
+ const gchar *name,
+ const gchar *address)
+{
+ if (response_time <= (time_t) 0)
+ response_time = time (NULL);
+
+ e_m365_json_begin_object_member (builder, NULL);
+
+ m365_json_utils_add_enum_as_json (builder, "type", type,
+ attendee_map, G_N_ELEMENTS (attendee_map),
+ E_M365_ATTENDEE_NOT_SET,
+ E_M365_ATTENDEE_NOT_SET);
+
+ e_m365_json_begin_object_member (builder, "status");
+
+ m365_json_utils_add_enum_as_json (builder, "response", response,
+ response_map, G_N_ELEMENTS (response_map),
+ E_M365_RESPONSE_NOT_SET,
+ E_M365_RESPONSE_UNKNOWN);
+
+ e_m365_add_date_time_offset_member (builder, "time", response_time);
+
+ e_m365_json_end_object_member (builder); /* status */
+
+ if ((name && *name) || (address && *address))
+ e_m365_add_email_address (builder, "emailAddress", name, address);
+
+ e_m365_json_end_object_member (builder);
+}
+
+void
+e_m365_event_add_null_attendees (JsonBuilder *builder)
+{
+ e_m365_json_add_null_member (builder, "attendees");
+}
+
+EM365ItemBody *
+e_m365_event_get_body (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "body");
+}
+
+void
+e_m365_event_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content)
+{
+ e_m365_add_item_body (builder, "body", content_type, content);
+}
+
+const gchar *
+e_m365_event_get_body_preview (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "bodyPreview", NULL);
+}
+
+JsonArray * /* const gchar * */
+e_m365_event_get_categories (EM365Event *event)
+{
+ return e_m365_json_get_array_member (event, "categories");
+}
+
+void
+e_m365_event_begin_categories (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_m365_event_end_categories (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_event_add_category (JsonBuilder *builder,
+ const gchar *category)
+{
+ g_return_if_fail (category && *category);
+
+ json_builder_add_string_value (builder, category);
+}
+
+time_t
+e_m365_event_get_created_date_time (EM365Event *event)
+{
+ return e_m365_get_date_time_offset_member (event, "createdDateTime");
+}
+
+EM365DateTimeWithZone *
+e_m365_event_get_end (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "end");
+}
+
+void
+e_m365_event_add_end (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "end", date_time, zone);
+}
+
+gboolean
+e_m365_event_get_has_attachments (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "hasAttachments", FALSE);
+}
+
+const gchar *
+e_m365_event_get_ical_uid (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "iCalUId", NULL);
+}
+
+EM365ImportanceType
+e_m365_event_get_importance (EM365Event *event)
+{
+ return m365_json_utils_get_json_as_enum (event, "importance",
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_UNKNOWN);
+}
+
+void
+e_m365_event_add_importance (JsonBuilder *builder,
+ EM365ImportanceType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "importance", value,
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_UNKNOWN);
+}
+
+gboolean
+e_m365_event_get_is_all_day (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "isAllDay", FALSE);
+}
+
+void
+e_m365_event_add_is_all_day (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isAllDay", value);
+}
+
+gboolean
+e_m365_event_get_is_cancelled (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "isCancelled", FALSE);
+}
+
+gboolean
+e_m365_event_get_is_online_meeting (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "isOnlineMeeting", FALSE);
+}
+
+void
+e_m365_event_add_is_online_meeting (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isOnlineMeeting", value);
+}
+
+gboolean
+e_m365_event_get_is_organizer (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "isOrganizer", FALSE);
+}
+
+gboolean
+e_m365_event_get_is_reminder_on (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "isReminderOn", FALSE);
+}
+
+void
+e_m365_event_add_is_reminder_on (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isReminderOn", value);
+}
+
+time_t
+e_m365_event_get_last_modified_date_time (EM365Event *event)
+{
+ return e_m365_get_date_time_offset_member (event, "lastModifiedDateTime");
+}
+
+EM365Location *
+e_m365_event_get_location (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "location");
+}
+
+void
+e_m365_event_begin_location (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "location");
+}
+
+void
+e_m365_event_end_location (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+void
+e_m365_event_add_null_location (JsonBuilder *builder)
+{
+ e_m365_json_add_null_member (builder, "location");
+}
+
+JsonArray * /* EM365Location * */
+e_m365_event_get_locations (EM365Event *event)
+{
+ return e_m365_json_get_array_member (event, "locations");
+}
+
+void
+e_m365_event_begin_locations (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "locations");
+}
+
+void
+e_m365_event_end_locations (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_event_begin_locations_location (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, NULL);
+}
+
+void
+e_m365_event_end_locations_location (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+EM365OnlineMeetingInfo *
+e_m365_event_get_online_meeting_info (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "onlineMeeting");
+}
+
+EM365OnlineMeetingProviderType
+e_m365_event_get_online_meeting_provider (EM365Event *event)
+{
+ return m365_json_utils_get_json_as_enum (event, "onlineMeetingProvider",
+ meeting_provider_map, G_N_ELEMENTS (meeting_provider_map),
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN);
+}
+
+void
+e_m365_event_add_online_meeting_provider (JsonBuilder *builder,
+ EM365OnlineMeetingProviderType value)
+{
+ if (value == E_M365_ONLINE_MEETING_PROVIDER_NOT_SET) {
+ e_m365_json_add_null_member (builder, "onlineMeetingProvider");
+ } else {
+ m365_json_utils_add_enum_as_json (builder, "onlineMeetingProvider", value,
+ meeting_provider_map, G_N_ELEMENTS (meeting_provider_map),
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN);
+ }
+}
+
+const gchar *
+e_m365_event_get_online_meeting_url (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "onlineMeetingUrl", NULL);
+}
+
+EM365Recipient *
+e_m365_event_get_organizer (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "organizer");
+}
+
+void
+e_m365_event_add_organizer (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address)
+{
+ e_m365_add_recipient (builder, "organizer", name, address);
+}
+
+void
+e_m365_event_add_null_organizer (JsonBuilder *builder)
+{
+ e_m365_json_add_null_member (builder, "organizer");
+}
+
+const gchar *
+e_m365_event_get_original_end_timezone (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "originalEndTimeZone", NULL);
+}
+
+time_t
+e_m365_event_get_original_start (EM365Event *event)
+{
+ return e_m365_get_date_time_offset_member (event, "originalStart");
+}
+
+const gchar *
+e_m365_event_get_original_start_timezone (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "originalStartTimeZone", NULL);
+}
+
+EM365PatternedRecurrence *
+e_m365_event_get_recurrence (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "recurrence");
+}
+
+void
+e_m365_event_begin_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "recurrence");
+}
+
+void
+e_m365_event_end_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+void
+e_m365_event_add_null_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_add_null_member (builder, "recurrence");
+}
+
+gint
+e_m365_event_get_reminder_minutes_before_start (EM365Event *event)
+{
+ return e_m365_json_get_int_member (event, "reminderMinutesBeforeStart", -1);
+}
+
+void
+e_m365_event_add_reminder_minutes_before_start (JsonBuilder *builder,
+ gint value)
+{
+ e_m365_json_add_int_member (builder, "reminderMinutesBeforeStart", value);
+}
+
+gboolean
+e_m365_event_get_response_requested (EM365Event *event)
+{
+ return e_m365_json_get_boolean_member (event, "responseRequested", FALSE);
+}
+
+void
+e_m365_event_add_response_requested (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "responseRequested", value);
+}
+
+
+EM365ResponseStatus *
+e_m365_event_get_response_status (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "responseStatus");
+}
+
+EM365SensitivityType
+e_m365_event_get_sensitivity (EM365Event *event)
+{
+ return m365_json_utils_get_json_as_enum (event, "sensitivity",
+ sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+ E_M365_SENSITIVITY_NOT_SET,
+ E_M365_SENSITIVITY_UNKNOWN);
+}
+
+void
+e_m365_event_add_sensitivity (JsonBuilder *builder,
+ EM365SensitivityType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "sensitivity", value,
+ sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+ E_M365_SENSITIVITY_NOT_SET,
+ E_M365_SENSITIVITY_UNKNOWN);
+}
+
+const gchar *
+e_m365_event_get_series_master_id (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "seriesMasterId", NULL);
+}
+
+EM365FreeBusyStatusType
+e_m365_event_get_show_as (EM365Event *event)
+{
+ return m365_json_utils_get_json_as_enum (event, "showAs",
+ free_busy_status_map, G_N_ELEMENTS (free_busy_status_map),
+ E_M365_FREE_BUSY_STATUS_NOT_SET,
+ E_M365_FREE_BUSY_STATUS_UNKNOWN);
+}
+
+void
+e_m365_event_add_show_as (JsonBuilder *builder,
+ EM365FreeBusyStatusType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "showAs", value,
+ free_busy_status_map, G_N_ELEMENTS (free_busy_status_map),
+ E_M365_FREE_BUSY_STATUS_NOT_SET,
+ E_M365_FREE_BUSY_STATUS_UNKNOWN);
+}
+
+EM365DateTimeWithZone *
+e_m365_event_get_start (EM365Event *event)
+{
+ return e_m365_json_get_object_member (event, "start");
+}
+
+void
+e_m365_event_add_start (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "start", date_time, zone);
+}
+
+const gchar *
+e_m365_event_get_subject (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "subject", NULL);
+}
+
+void
+e_m365_event_add_subject (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "subject", value ? value : "");
+}
+
+EM365EventTypeType
+e_m365_event_get_type (EM365Event *event)
+{
+ return m365_json_utils_get_json_as_enum (event, "type",
+ event_type_map, G_N_ELEMENTS (event_type_map),
+ E_M365_EVENT_TYPE_NOT_SET,
+ E_M365_EVENT_TYPE_UNKNOWN);
+}
+
+const gchar *
+e_m365_event_get_web_link (EM365Event *event)
+{
+ return e_m365_json_get_string_member (event, "webLink", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/freebusyerror?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_free_busy_error_get_message (EM365FreeBusyError *fberror)
+{
+ return e_m365_json_get_string_member (fberror, "message", NULL);
+}
+
+const gchar *
+e_m365_free_busy_error_get_response_code (EM365FreeBusyError *fberror)
+{
+ return e_m365_json_get_string_member (fberror, "responseCode", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/scheduleitem?view=graph-rest-1.0 */
+
+EM365DateTimeWithZone *
+e_m365_schedule_item_get_end (EM365ScheduleItem *schitem)
+{
+ return e_m365_json_get_object_member (schitem, "end");
+}
+
+gboolean
+e_m365_schedule_item_get_is_private (EM365ScheduleItem *schitem)
+{
+ return e_m365_json_get_boolean_member (schitem, "isPrivate", FALSE);
+}
+
+const gchar *
+e_m365_schedule_item_get_location (EM365ScheduleItem *schitem)
+{
+ return e_m365_json_get_string_member (schitem, "location", NULL);
+}
+
+EM365DateTimeWithZone *
+e_m365_schedule_item_get_start (EM365ScheduleItem *schitem)
+{
+ return e_m365_json_get_object_member (schitem, "start");
+}
+
+EM365FreeBusyStatusType
+e_m365_schedule_item_get_status (EM365ScheduleItem *schitem)
+{
+ return m365_json_utils_get_json_as_enum (schitem, "status",
+ free_busy_status_map, G_N_ELEMENTS (free_busy_status_map),
+ E_M365_FREE_BUSY_STATUS_NOT_SET,
+ E_M365_FREE_BUSY_STATUS_UNKNOWN);
+}
+
+const gchar *
+e_m365_schedule_item_get_subject (EM365ScheduleItem *schitem)
+{
+ return e_m365_json_get_string_member (schitem, "subject", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/workinghours?view=graph-rest-1.0 */
+
+JsonArray * /* Use e_m365_array_get_day_of_week_element() to get the items */
+e_m365_working_hours_get_days_of_week (EM365WorkingHours *wrkhrs)
+{
+ return e_m365_json_get_array_member (wrkhrs, "daysOfWeek");
+}
+
+EM365TimeOfDay
+e_m365_working_hours_get_start_time (EM365WorkingHours *wrkhrs)
+{
+ return e_m365_time_of_day_get (wrkhrs, "startTime");
+}
+
+EM365TimeOfDay
+e_m365_working_hours_get_end_time (EM365WorkingHours *wrkhrs)
+{
+ return e_m365_time_of_day_get (wrkhrs, "endTime");
+}
+
+const gchar *
+e_m365_working_hours_get_time_zone_name (EM365WorkingHours *wrkhrs)
+{
+ JsonObject *value;
+
+ value = e_m365_json_get_object_member (wrkhrs, "timeZone");
+
+ if (!value)
+ return NULL;
+
+ /* https://docs.microsoft.com/en-us/graph/api/resources/timezonebase?view=graph-rest-1.0 */
+
+ return e_m365_json_get_string_member (value, "name", NULL);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/scheduleinformation?view=graph-rest-1.0 */
+
+const gchar *
+e_m365_schedule_information_get_availability_view (EM365ScheduleInformation *schinfo)
+{
+ return e_m365_json_get_string_member (schinfo, "availabilityView", NULL);
+}
+
+EM365FreeBusyError *
+e_m365_schedule_information_get_free_busy_error (EM365ScheduleInformation *schinfo)
+{
+ return e_m365_json_get_object_member (schinfo, "error");
+}
+
+const gchar *
+e_m365_schedule_information_get_schedule_id (EM365ScheduleInformation *schinfo)
+{
+ return e_m365_json_get_string_member (schinfo, "scheduleId", NULL);
+}
+
+JsonArray * /* EM365ScheduleItem * */
+e_m365_schedule_information_get_schedule_items (EM365ScheduleInformation *schinfo)
+{
+ return e_m365_json_get_array_member (schinfo, "scheduleItems");
+}
+
+EM365WorkingHours *
+e_m365_schedule_information_get_working_hours (EM365ScheduleInformation *schinfo)
+{
+ return e_m365_json_get_object_member (schinfo, "workingHours");
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktaskfolder?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_folder_get_id (EM365TaskFolder *folder)
+{
+ return e_m365_json_get_string_member (folder, "id", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_change_key (EM365TaskFolder *folder)
+{
+ return e_m365_json_get_string_member (folder, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_parent_group_key (EM365TaskFolder *folder)
+{
+ return e_m365_json_get_string_member (folder, "parentGroupKey", NULL);
+}
+
+const gchar *
+e_m365_task_folder_get_name (EM365TaskFolder *folder)
+{
+ return e_m365_json_get_string_member (folder, "name", NULL);
+}
+
+gboolean
+e_m365_task_folder_get_is_default_folder (EM365TaskFolder *folder)
+{
+ return e_m365_json_get_boolean_member (folder, "isDefaultFolder", FALSE);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktaskgroup?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_group_get_id (EM365TaskGroup *group)
+{
+ return e_m365_json_get_string_member (group, "id", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_change_key (EM365TaskGroup *group)
+{
+ return e_m365_json_get_string_member (group, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_group_key (EM365TaskGroup *group)
+{
+ return e_m365_json_get_string_member (group, "groupKey", NULL);
+}
+
+const gchar *
+e_m365_task_group_get_name (EM365TaskGroup *group)
+{
+ return e_m365_json_get_string_member (group, "name", NULL);
+}
+
+gboolean
+e_m365_task_group_get_is_default_group (EM365TaskGroup *group)
+{
+ return e_m365_json_get_boolean_member (group, "isDefaultGroup", FALSE);
+}
+
+/* https://docs.microsoft.com/en-us/graph/api/resources/outlooktask?view=graph-rest-beta */
+
+const gchar *
+e_m365_task_get_id (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "id", NULL);
+}
+
+const gchar *
+e_m365_task_get_change_key (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "changeKey", NULL);
+}
+
+const gchar *
+e_m365_task_get_parent_folder_id (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "parentFolderId", NULL);
+}
+
+const gchar *
+e_m365_task_get_assigned_to (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "assignedTo", NULL);
+}
+
+EM365ItemBody *
+e_m365_task_get_body (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "body");
+}
+
+void
+e_m365_task_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content)
+{
+ e_m365_add_item_body (builder, "body", content_type, content);
+}
+
+JsonArray * /* const gchar * */
+e_m365_task_get_categories (EM365Task *task)
+{
+ return e_m365_json_get_array_member (task, "categories");
+}
+
+void
+e_m365_task_begin_categories (JsonBuilder *builder)
+{
+ e_m365_json_begin_array_member (builder, "categories");
+}
+
+void
+e_m365_task_end_categories (JsonBuilder *builder)
+{
+ e_m365_json_end_array_member (builder);
+}
+
+void
+e_m365_task_add_category (JsonBuilder *builder,
+ const gchar *category)
+{
+ g_return_if_fail (category && *category);
+
+ json_builder_add_string_value (builder, category);
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_completed_date_time (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "completedDateTime");
+}
+
+void
+e_m365_task_add_completed_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "completedDateTime", date_time, zone);
+}
+
+time_t
+e_m365_task_get_created_date_time (EM365Task *task)
+{
+ return e_m365_get_date_time_offset_member (task, "createdDateTime");
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_due_date_time (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "dueDateTime");
+}
+
+void
+e_m365_task_add_due_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "dueDateTime", date_time, zone);
+}
+
+gboolean
+e_m365_task_get_has_attachments (EM365Task *task)
+{
+ return e_m365_json_get_boolean_member (task, "hasAttachments", FALSE);
+}
+
+EM365ImportanceType
+e_m365_task_get_importance (EM365Task *task)
+{
+ return m365_json_utils_get_json_as_enum (task, "importance",
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_UNKNOWN);
+}
+
+void
+e_m365_task_add_importance (JsonBuilder *builder,
+ EM365ImportanceType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "importance", value,
+ importance_map, G_N_ELEMENTS (importance_map),
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_NOT_SET);
+}
+
+gboolean
+e_m365_task_get_is_reminder_on (EM365Task *task)
+{
+ return e_m365_json_get_boolean_member (task, "isReminderOn", FALSE);
+}
+
+void
+e_m365_task_add_is_reminder_on (JsonBuilder *builder,
+ gboolean value)
+{
+ e_m365_json_add_boolean_member (builder, "isReminderOn", value);
+}
+
+time_t
+e_m365_task_get_last_modified_date_time (EM365Task *task)
+{
+ return e_m365_get_date_time_offset_member (task, "lastModifiedDateTime");
+}
+
+const gchar *
+e_m365_task_get_owner (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "owner", NULL);
+}
+
+void
+e_m365_task_add_owner (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "owner", value);
+}
+
+EM365PatternedRecurrence *
+e_m365_task_get_recurrence (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "recurrence");
+}
+
+void
+e_m365_task_begin_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_begin_object_member (builder, "recurrence");
+}
+
+void
+e_m365_task_end_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_end_object_member (builder);
+}
+
+void
+e_m365_task_add_null_recurrence (JsonBuilder *builder)
+{
+ e_m365_json_add_null_member (builder, "recurrence");
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_reminder_date_time (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "reminderDateTime");
+}
+
+void
+e_m365_task_add_reminder_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "reminderDateTime", date_time, zone);
+}
+
+EM365SensitivityType
+e_m365_task_get_sensitivity (EM365Task *task)
+{
+ return m365_json_utils_get_json_as_enum (task, "sensitivity",
+ sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+ E_M365_SENSITIVITY_NOT_SET,
+ E_M365_SENSITIVITY_UNKNOWN);
+}
+
+void
+e_m365_task_add_sensitivity (JsonBuilder *builder,
+ EM365SensitivityType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "sensitivity", value,
+ sensitivity_map, G_N_ELEMENTS (sensitivity_map),
+ E_M365_SENSITIVITY_NOT_SET,
+ E_M365_SENSITIVITY_UNKNOWN);
+}
+
+EM365DateTimeWithZone *
+e_m365_task_get_start_date_time (EM365Task *task)
+{
+ return e_m365_json_get_object_member (task, "startDateTime");
+}
+
+void
+e_m365_task_add_start_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone)
+{
+ e_m365_add_date_time (builder, "startDateTime", date_time, zone);
+}
+
+EM365StatusType
+e_m365_task_get_status (EM365Task *task)
+{
+ return m365_json_utils_get_json_as_enum (task, "status",
+ status_map, G_N_ELEMENTS (status_map),
+ E_M365_STATUS_NOT_SET,
+ E_M365_STATUS_UNKNOWN);
+}
+
+void
+e_m365_task_add_status (JsonBuilder *builder,
+ EM365StatusType value)
+{
+ m365_json_utils_add_enum_as_json (builder, "status", value,
+ status_map, G_N_ELEMENTS (status_map),
+ E_M365_STATUS_NOT_SET,
+ E_M365_STATUS_UNKNOWN);
+}
+
+const gchar *
+e_m365_task_get_subject (EM365Task *task)
+{
+ return e_m365_json_get_string_member (task, "subject", NULL);
+}
+
+void
+e_m365_task_add_subject (JsonBuilder *builder,
+ const gchar *value)
+{
+ e_m365_json_add_string_member (builder, "subject", value);
+}
diff --git a/src/Microsoft365/common/e-m365-json-utils.h b/src/Microsoft365/common/e-m365-json-utils.h
new file mode 100644
index 00000000..df5a0548
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-json-utils.h
@@ -0,0 +1,1056 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_JSON_UTILS_H
+#define E_M365_JSON_UTILS_H
+
+#include <time.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+/* Just for better readability */
+#define EM365Attachment JsonObject
+#define EM365Attendee JsonObject
+#define EM365Calendar JsonObject
+#define EM365CalendarGroup JsonObject
+#define EM365Category JsonObject
+#define EM365Contact JsonObject
+#define EM365Date gint
+#define EM365DateTimeWithZone JsonObject
+#define EM365EmailAddress JsonObject
+#define EM365Event JsonObject
+#define EM365Folder JsonObject
+#define EM365FollowupFlag JsonObject
+#define EM365FreeBusyError JsonObject
+#define EM365InternetMessageHeader JsonObject
+#define EM365ItemBody JsonObject
+#define EM365Location JsonObject
+#define EM365MailFolder JsonObject
+#define EM365MailMessage JsonObject
+#define EM365OnlineMeetingInfo JsonObject
+#define EM365OutlookGeoCoordinates JsonObject
+#define EM365PatternedRecurrence JsonObject
+#define EM365Phone JsonObject
+#define EM365PhysicalAddress JsonObject
+#define EM365Recipient JsonObject
+#define EM365RecurrencePattern JsonObject
+#define EM365RecurrenceRange JsonObject
+#define EM365ResponseStatus JsonObject
+#define EM365ScheduleInformation JsonObject
+#define EM365ScheduleItem JsonObject
+#define EM365Task JsonObject
+#define EM365TaskFolder JsonObject
+#define EM365TaskGroup JsonObject
+#define EM365TimeOfDay gint64
+#define EM365WorkingHours JsonObject
+
+typedef enum _EM365AttachmentDataType {
+ E_M365_ATTACHMENT_DATA_TYPE_NOT_SET,
+ E_M365_ATTACHMENT_DATA_TYPE_UNKNOWN,
+ E_M365_ATTACHMENT_DATA_TYPE_FILE,
+ E_M365_ATTACHMENT_DATA_TYPE_ITEM,
+ E_M365_ATTACHMENT_DATA_TYPE_REFERENCE
+} EM365AttachmentDataType;
+
+typedef enum _EM365AttendeeType {
+ E_M365_ATTENDEE_NOT_SET,
+ E_M365_ATTENDEE_UNKNOWN,
+ E_M365_ATTENDEE_REQUIRED,
+ E_M365_ATTENDEE_OPTIONAL,
+ E_M365_ATTENDEE_RESOURCE
+} EM365AttendeeType;
+
+typedef enum _EM365CalendarColorType {
+ E_M365_CALENDAR_COLOR_NOT_SET = -3,
+ E_M365_CALENDAR_COLOR_UNKNOWN = -2,
+ E_M365_CALENDAR_COLOR_AUTO = -1,
+ E_M365_CALENDAR_COLOR_LIGHT_BLUE = 0,
+ E_M365_CALENDAR_COLOR_LIGHT_GREEN = 1,
+ E_M365_CALENDAR_COLOR_LIGHT_ORANGE = 2,
+ E_M365_CALENDAR_COLOR_LIGHT_GRAY = 3,
+ E_M365_CALENDAR_COLOR_LIGHT_YELLOW = 4,
+ E_M365_CALENDAR_COLOR_LIGHT_TEAL = 5,
+ E_M365_CALENDAR_COLOR_LIGHT_PINK = 6,
+ E_M365_CALENDAR_COLOR_LIGHT_BROWN = 7,
+ E_M365_CALENDAR_COLOR_LIGHT_RED = 8,
+ E_M365_CALENDAR_COLOR_MAX_COLOR = 9
+} EM365CalendarColorType;
+
+typedef enum _EM365DayOfWeekType {
+ E_M365_DAY_OF_WEEK_NOT_SET,
+ E_M365_DAY_OF_WEEK_UNKNOWN,
+ E_M365_DAY_OF_WEEK_SUNDAY,
+ E_M365_DAY_OF_WEEK_MONDAY,
+ E_M365_DAY_OF_WEEK_TUESDAY,
+ E_M365_DAY_OF_WEEK_WEDNESDAY,
+ E_M365_DAY_OF_WEEK_THURSDAY,
+ E_M365_DAY_OF_WEEK_FRIDAY,
+ E_M365_DAY_OF_WEEK_SATURDAY
+} EM365DayOfWeekType;
+
+typedef enum _EM365EventTypeType {
+ E_M365_EVENT_TYPE_NOT_SET,
+ E_M365_EVENT_TYPE_UNKNOWN,
+ E_M365_EVENT_TYPE_SINGLE_INSTANCE,
+ E_M365_EVENT_TYPE_OCCURRENCE,
+ E_M365_EVENT_TYPE_EXCEPTION,
+ E_M365_EVENT_TYPE_SERIES_MASTER
+} EM365EventTypeType;
+
+typedef enum _EM365FollowupFlagStatusType {
+ E_M365_FOLLOWUP_FLAG_STATUS_NOT_SET,
+ E_M365_FOLLOWUP_FLAG_STATUS_UNKNOWN,
+ E_M365_FOLLOWUP_FLAG_STATUS_NOT_FLAGGED,
+ E_M365_FOLLOWUP_FLAG_STATUS_COMPLETE,
+ E_M365_FOLLOWUP_FLAG_STATUS_FLAGGED
+} EM365FollowupFlagStatusType;
+
+typedef enum _EM365FreeBusyStatusType {
+ E_M365_FREE_BUSY_STATUS_NOT_SET,
+ E_M365_FREE_BUSY_STATUS_UNKNOWN,
+ E_M365_FREE_BUSY_STATUS_FREE,
+ E_M365_FREE_BUSY_STATUS_TENTATIVE,
+ E_M365_FREE_BUSY_STATUS_BUSY,
+ E_M365_FREE_BUSY_STATUS_OOF,
+ E_M365_FREE_BUSY_STATUS_WORKING_ELSEWHERE
+} EM365FreeBusyStatusType;
+
+typedef enum _EM365ImportanceType {
+ E_M365_IMPORTANCE_NOT_SET,
+ E_M365_IMPORTANCE_UNKNOWN,
+ E_M365_IMPORTANCE_LOW,
+ E_M365_IMPORTANCE_NORMAL,
+ E_M365_IMPORTANCE_HIGH
+} EM365ImportanceType;
+
+typedef enum _EM365InferenceClassificationType {
+ E_M365_INFERENCE_CLASSIFICATION_NOT_SET,
+ E_M365_INFERENCE_CLASSIFICATION_UNKNOWN,
+ E_M365_INFERENCE_CLASSIFICATION_FOCUSED,
+ E_M365_INFERENCE_CLASSIFICATION_OTHER
+} EM365InferenceClassificationType;
+
+typedef enum _EM365ItemBodyContentTypeType {
+ E_M365_ITEM_BODY_CONTENT_TYPE_NOT_SET,
+ E_M365_ITEM_BODY_CONTENT_TYPE_UNKNOWN,
+ E_M365_ITEM_BODY_CONTENT_TYPE_TEXT,
+ E_M365_ITEM_BODY_CONTENT_TYPE_HTML
+} EM365ItemBodyContentTypeType;
+
+typedef enum _EM365LocationType {
+ E_M365_LOCATION_NOT_SET,
+ E_M365_LOCATION_UNKNOWN,
+ E_M365_LOCATION_DEFAULT,
+ E_M365_LOCATION_CONFERENCE_ROOM,
+ E_M365_LOCATION_HOME_ADDRESS,
+ E_M365_LOCATION_BUSINESS_ADDRESS,
+ E_M365_LOCATION_GEO_COORDINATES,
+ E_M365_LOCATION_STREET_ADDRESS,
+ E_M365_LOCATION_HOTEL,
+ E_M365_LOCATION_RESTAURANT,
+ E_M365_LOCATION_LOCAL_BUSINESS,
+ E_M365_LOCATION_POSTAL_ADDRESS
+} EM365LocationType;
+
+typedef enum _EM365OnlineMeetingProviderType {
+ E_M365_ONLINE_MEETING_PROVIDER_NOT_SET = -1,
+ E_M365_ONLINE_MEETING_PROVIDER_UNKNOWN = 0,
+ E_M365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_BUSINESS = 1 << 0,
+ E_M365_ONLINE_MEETING_PROVIDER_SKYPE_FOR_CONSUMER = 1 << 1,
+ E_M365_ONLINE_MEETING_PROVIDER_TEAMS_FOR_BUSINESS = 1 << 2
+} EM365OnlineMeetingProviderType;
+
+typedef enum _EM365PhoneType {
+ E_M365_PHONE_NOT_SET,
+ E_M365_PHONE_UNKNOWN,
+ E_M365_PHONE_HOME,
+ E_M365_PHONE_BUSINESS,
+ E_M365_PHONE_MOBILE,
+ E_M365_PHONE_OTHER,
+ E_M365_PHONE_ASSISTANT,
+ E_M365_PHONE_HOMEFAX,
+ E_M365_PHONE_BUSINESSFAX,
+ E_M365_PHONE_OTHERFAX,
+ E_M365_PHONE_PAGER,
+ E_M365_PHONE_RADIO
+} EM365PhoneType;
+
+typedef enum _EM365RecurrencePatternType {
+ E_M365_RECURRENCE_PATTERN_NOT_SET,
+ E_M365_RECURRENCE_PATTERN_UNKNOWN,
+ E_M365_RECURRENCE_PATTERN_DAILY,
+ E_M365_RECURRENCE_PATTERN_WEEKLY,
+ E_M365_RECURRENCE_PATTERN_ABSOLUTE_MONTHLY,
+ E_M365_RECURRENCE_PATTERN_RELATIVE_MONTHLY,
+ E_M365_RECURRENCE_PATTERN_ABSOLUTE_YEARLY,
+ E_M365_RECURRENCE_PATTERN_RELATIVE_YEARLY
+} EM365RecurrencePatternType;
+
+typedef enum _EM365RecurrenceRangeType {
+ E_M365_RECURRENCE_RANGE_NOT_SET,
+ E_M365_RECURRENCE_RANGE_UNKNOWN,
+ E_M365_RECURRENCE_RANGE_ENDDATE,
+ E_M365_RECURRENCE_RANGE_NOEND,
+ E_M365_RECURRENCE_RANGE_NUMBERED
+} EM365RecurrenceRangeType;
+
+typedef enum _EM365ResponseType {
+ E_M365_RESPONSE_NOT_SET,
+ E_M365_RESPONSE_UNKNOWN,
+ E_M365_RESPONSE_NONE,
+ E_M365_RESPONSE_ORGANIZER,
+ E_M365_RESPONSE_TENTATIVELY_ACCEPTED,
+ E_M365_RESPONSE_ACCEPTED,
+ E_M365_RESPONSE_DECLINED,
+ E_M365_RESPONSE_NOT_RESPONDED
+} EM365ResponseType;
+
+typedef enum _EM365SensitivityType {
+ E_M365_SENSITIVITY_NOT_SET,
+ E_M365_SENSITIVITY_UNKNOWN,
+ E_M365_SENSITIVITY_NORMAL,
+ E_M365_SENSITIVITY_PERSONAL,
+ E_M365_SENSITIVITY_PRIVATE,
+ E_M365_SENSITIVITY_CONFIDENTIAL
+} EM365SensitivityType;
+
+typedef enum _EM365StatusType {
+ E_M365_STATUS_NOT_SET,
+ E_M365_STATUS_UNKNOWN,
+ E_M365_STATUS_NOT_STARTED,
+ E_M365_STATUS_IN_PROGRESS,
+ E_M365_STATUS_COMPLETED,
+ E_M365_STATUS_WAITING_ON_OTHERS,
+ E_M365_STATUS_DEFERRED
+} EM365StatusType;
+
+typedef enum _EM365WeekIndexType {
+ E_M365_WEEK_INDEX_NOT_SET,
+ E_M365_WEEK_INDEX_UNKNOWN,
+ E_M365_WEEK_INDEX_FIRST,
+ E_M365_WEEK_INDEX_SECOND,
+ E_M365_WEEK_INDEX_THIRD,
+ E_M365_WEEK_INDEX_FOURTH,
+ E_M365_WEEK_INDEX_LAST
+} EM365WeekIndexType;
+
+const gchar * e_m365_calendar_color_to_rgb (EM365CalendarColorType color);
+EM365CalendarColorType
+ e_m365_rgb_to_calendar_color (const gchar *rgb);
+
+JsonArray * e_m365_json_get_array_member (JsonObject *object,
+ const gchar *member_name);
+void e_m365_json_begin_array_member (JsonBuilder *builder,
+ const gchar *member_name);
+void e_m365_json_end_array_member (JsonBuilder *builder);
+gboolean e_m365_json_get_boolean_member (JsonObject *object,
+ const gchar *member_name,
+ gboolean default_value);
+void e_m365_json_add_boolean_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gboolean value);
+gdouble e_m365_json_get_double_member (JsonObject *object,
+ const gchar *member_name,
+ gdouble default_value);
+void e_m365_json_add_double_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gdouble value);
+gint64 e_m365_json_get_int_member (JsonObject *object,
+ const gchar *member_name,
+ gint64 default_value);
+void e_m365_json_add_int_member (JsonBuilder *builder,
+ const gchar *member_name,
+ gint64 value);
+gboolean e_m365_json_get_null_member (JsonObject *object,
+ const gchar *member_name,
+ gboolean default_value);
+void e_m365_json_add_null_member (JsonBuilder *builder,
+ const gchar *member_name);
+JsonObject * e_m365_json_get_object_member (JsonObject *object,
+ const gchar *member_name);
+void e_m365_json_begin_object_member (JsonBuilder *builder,
+ const gchar *member_name);
+void e_m365_json_end_object_member (JsonBuilder *builder);
+const gchar * e_m365_json_get_string_member (JsonObject *object,
+ const gchar *member_name,
+ const gchar *default_value);
+void e_m365_json_add_string_member (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value);
+void e_m365_json_add_nonempty_string_member (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value);
+void e_m365_json_add_nonempty_or_null_string_member
+ (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *value);
+
+EM365Date e_m365_date_get (JsonObject *object,
+ const gchar *member_name);
+void e_m365_add_date (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365Date value);
+gboolean e_m365_date_decode (EM365Date dt,
+ gint *out_year,
+ gint *out_month,
+ gint *out_day);
+EM365Date e_m365_date_encode (gint year,
+ gint month,
+ gint day);
+EM365TimeOfDay e_m365_time_of_day_get (JsonObject *object,
+ const gchar *member_name);
+void e_m365_add_time_of_day (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365TimeOfDay value);
+gboolean e_m365_time_of_day_decode (EM365TimeOfDay tod,
+ gint *out_hour,
+ gint *out_minute,
+ gint *out_second,
+ gint *out_fraction);
+EM365TimeOfDay e_m365_time_of_day_encode (gint hour,
+ gint minute,
+ gint second,
+ gint fraction);
+
+time_t e_m365_get_date_time_offset_member (JsonObject *object,
+ const gchar *member_name);
+void e_m365_add_date_time_offset_member (JsonBuilder *builder,
+ const gchar *member_name,
+ time_t value);
+
+time_t e_m365_date_time_get_date_time (EM365DateTimeWithZone *datetime);
+const gchar * e_m365_date_time_get_time_zone (EM365DateTimeWithZone *datetime);
+void e_m365_add_date_time (JsonBuilder *builder,
+ const gchar *member_name,
+ time_t date_time,
+ const gchar *zone);
+
+gboolean e_m365_delta_is_removed_object (JsonObject *object);
+
+const gchar * e_m365_category_get_display_name (EM365Category *category);
+const gchar * e_m365_category_get_id (EM365Category *category);
+const gchar * e_m365_category_get_color (EM365Category *category);
+
+const gchar * e_m365_folder_get_id (EM365Folder *folder);
+const gchar * e_m365_folder_get_parent_folder_id (EM365Folder *folder);
+const gchar * e_m365_folder_get_display_name (EM365Folder *folder);
+
+gint32 e_m365_mail_folder_get_child_folder_count
+ (EM365MailFolder *folder);
+gint32 e_m365_mail_folder_get_total_item_count (EM365MailFolder *folder);
+gint32 e_m365_mail_folder_get_unread_item_count(EM365MailFolder *folder);
+
+const gchar * e_m365_recipient_get_name (EM365Recipient *recipient);
+const gchar * e_m365_recipient_get_address (EM365Recipient *recipient);
+void e_m365_add_recipient (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *name,
+ const gchar *address);
+
+const gchar * e_m365_internet_message_header_get_name (EM365InternetMessageHeader *header);
+const gchar * e_m365_internet_message_header_get_value(EM365InternetMessageHeader *header);
+void e_m365_add_internet_message_header (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *value);
+
+EM365DateTimeWithZone *
+ e_m365_followup_flag_get_completed_date_time
+ (EM365FollowupFlag *flag);
+void e_m365_followup_flag_add_completed_date_time
+ (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+EM365DateTimeWithZone *
+ e_m365_followup_flag_get_due_date_time (EM365FollowupFlag *flag);
+void e_m365_followup_flag_add_due_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+EM365FollowupFlagStatusType
+ e_m365_followup_flag_get_flag_status (EM365FollowupFlag *flag);
+void e_m365_followup_flag_add_flag_status (JsonBuilder *builder,
+ EM365FollowupFlagStatusType status);
+EM365DateTimeWithZone *
+ e_m365_followup_flag_get_start_date_time(EM365FollowupFlag *flag);
+void e_m365_followup_flag_add_start_date_time(JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+
+const gchar * e_m365_item_body_get_content (EM365ItemBody *item_body);
+EM365ItemBodyContentTypeType
+ e_m365_item_body_get_content_type (EM365ItemBody *item_body);
+void e_m365_add_item_body (JsonBuilder *builder,
+ const gchar *member_name,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content);
+
+JsonArray * e_m365_mail_message_get_bcc_recipients (EM365MailMessage *mail); /* EM365Recipient * */
+void e_m365_mail_message_begin_bcc_recipients(JsonBuilder *builder);
+void e_m365_mail_message_end_bcc_recipients (JsonBuilder *builder);
+EM365ItemBody * e_m365_mail_message_get_body (EM365MailMessage *mail);
+void e_m365_mail_message_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content);
+const gchar * e_m365_mail_message_get_body_preview (EM365MailMessage *mail);
+JsonArray * e_m365_mail_message_get_categories (EM365MailMessage *mail); /* const gchar * */
+void e_m365_mail_message_begin_categories (JsonBuilder *builder);
+void e_m365_mail_message_end_categories (JsonBuilder *builder);
+void e_m365_mail_message_add_category (JsonBuilder *builder,
+ const gchar *category);
+JsonArray * e_m365_mail_message_get_cc_recipients (EM365MailMessage *mail); /* EM365Recipient * */
+void e_m365_mail_message_begin_cc_recipients (JsonBuilder *builder);
+void e_m365_mail_message_end_cc_recipients (JsonBuilder *builder);
+const gchar * e_m365_mail_message_get_change_key (EM365MailMessage *mail);
+const gchar * e_m365_mail_message_get_conversation_id (EM365MailMessage *mail);
+JsonObject * e_m365_mail_message_get_conversation_index
+ (EM365MailMessage *mail);
+time_t e_m365_mail_message_get_created_date_time
+ (EM365MailMessage *mail);
+EM365FollowupFlag *
+ e_m365_mail_message_get_flag (EM365MailMessage *mail);
+void e_m365_mail_message_begin_flag (JsonBuilder *builder);
+void e_m365_mail_message_end_flag (JsonBuilder *builder);
+EM365Recipient *
+ e_m365_mail_message_get_from (EM365MailMessage *mail);
+void e_m365_mail_message_add_from (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address);
+gboolean e_m365_mail_message_get_has_attachments (EM365MailMessage *mail);
+const gchar * e_m365_mail_message_get_id (EM365MailMessage *mail);
+EM365ImportanceType
+ e_m365_mail_message_get_importance (EM365MailMessage *mail);
+void e_m365_mail_message_add_importance (JsonBuilder *builder,
+ EM365ImportanceType importance);
+EM365InferenceClassificationType
+ e_m365_mail_message_get_inference_classification
+ (EM365MailMessage *mail);
+JsonArray * e_m365_mail_message_get_internet_message_headers
+ (EM365MailMessage *mail); /*
EM365InternetMessageHeader * */
+void e_m365_mail_message_begin_internet_message_headers
+ (JsonBuilder *builder);
+void e_m365_mail_message_end_internet_message_headers
+ (JsonBuilder *builder);
+const gchar * e_m365_mail_message_get_internet_message_id
+ (EM365MailMessage *mail);
+void e_m365_mail_message_add_internet_message_id
+ (JsonBuilder *builder,
+ const gchar *message_id);
+gboolean e_m365_mail_message_get_is_delivery_receipt_requested
+ (EM365MailMessage *mail);
+void e_m365_mail_message_add_is_delivery_receipt_requested
+ (JsonBuilder *builder,
+ gboolean value);
+gboolean e_m365_mail_message_get_is_draft (EM365MailMessage *mail);
+gboolean e_m365_mail_message_get_is_read (EM365MailMessage *mail);
+void e_m365_mail_message_add_is_read (JsonBuilder *builder,
+ gboolean value);
+gboolean e_m365_mail_message_get_is_read_receipt_requested
+ (EM365MailMessage *mail);
+void e_m365_mail_message_add_is_read_receipt_requested
+ (JsonBuilder *builder,
+ gboolean value);
+time_t e_m365_mail_message_get_last_modified_date_time
+ (EM365MailMessage *mail);
+const gchar * e_m365_mail_message_get_parent_folder_id(EM365MailMessage *mail);
+time_t e_m365_mail_message_get_received_date_time
+ (EM365MailMessage *mail);
+void e_m365_mail_message_add_received_date_time
+ (JsonBuilder *builder,
+ time_t value);
+JsonArray * e_m365_mail_message_get_reply_to (EM365MailMessage *mail); /* EM365Recipient * */
+void e_m365_mail_message_begin_reply_to (JsonBuilder *builder);
+void e_m365_mail_message_end_reply_to (JsonBuilder *builder);
+EM365Recipient *e_m365_mail_message_get_sender (EM365MailMessage *mail);
+void e_m365_mail_message_add_sender (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address);
+time_t e_m365_mail_message_get_sent_date_time (EM365MailMessage *mail);
+void e_m365_mail_message_add_sent_date_time (JsonBuilder *builder,
+ time_t value);
+const gchar * e_m365_mail_message_get_subject (EM365MailMessage *mail);
+void e_m365_mail_message_add_subject (JsonBuilder *builder,
+ const gchar *subject);
+JsonArray * e_m365_mail_message_get_to_recipients (EM365MailMessage *mail); /* EM365Recipient * */
+void e_m365_mail_message_begin_to_recipients (JsonBuilder *builder);
+void e_m365_mail_message_end_to_recipients (JsonBuilder *builder);
+EM365ItemBody * e_m365_mail_message_get_unique_body (EM365MailMessage *mail);
+const gchar * e_m365_mail_message_get_web_link (EM365MailMessage *mail);
+
+EM365AttachmentDataType
+ e_m365_attachment_get_data_type (EM365Attachment *attachment);
+void e_m365_attachment_begin_attachment (JsonBuilder *builder,
+ EM365AttachmentDataType data_type);
+void e_m365_attachment_end_attachment (JsonBuilder *builder);
+const gchar * e_m365_attachment_get_content_type (EM365Attachment *attachment);
+void e_m365_attachment_add_content_type (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_attachment_get_id (EM365Attachment *attachment);
+gboolean e_m365_attachment_get_is_inline (EM365Attachment *attachment);
+void e_m365_attachment_add_is_inline (JsonBuilder *builder,
+ gboolean value);
+time_t e_m365_attachment_get_last_modified_date_time
+ (EM365Attachment *attachment);
+void e_m365_attachment_add_last_modified_date_time
+ (JsonBuilder *builder,
+ time_t value);
+const gchar * e_m365_attachment_get_name (EM365Attachment *attachment);
+void e_m365_attachment_add_name (JsonBuilder *builder,
+ const gchar *value);
+gint32 e_m365_attachment_get_size (EM365Attachment *attachment);
+void e_m365_attachment_add_size (JsonBuilder *builder,
+ gint32 value);
+const gchar * e_m365_file_attachment_get_content_bytes(EM365Attachment *attachment); /* base64-encoded */
+void e_m365_file_attachment_add_content_bytes(JsonBuilder *builder,
+ const gchar *base64_value);
+const gchar * e_m365_file_attachment_get_content_id (EM365Attachment *attachment);
+void e_m365_file_attachment_add_content_id (JsonBuilder *builder,
+ const gchar *value);
+
+const gchar * e_m365_email_address_get_name (EM365EmailAddress *email);
+const gchar * e_m365_email_address_get_address (EM365EmailAddress *email);
+void e_m365_add_email_address (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *name,
+ const gchar *address);
+const gchar * e_m365_physical_address_get_city (EM365PhysicalAddress *address);
+const gchar * e_m365_physical_address_get_country_or_region
+ (EM365PhysicalAddress *address);
+const gchar * e_m365_physical_address_get_postal_code (EM365PhysicalAddress *address);
+const gchar * e_m365_physical_address_get_state (EM365PhysicalAddress *address);
+const gchar * e_m365_physical_address_get_street (EM365PhysicalAddress *address);
+void e_m365_add_physical_address (JsonBuilder *builder,
+ const gchar *member_name,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street);
+
+const gchar * e_m365_contact_get_id (EM365Contact *contact);
+const gchar * e_m365_contact_get_parent_folder_id (EM365Contact *contact);
+const gchar * e_m365_contact_get_change_key (EM365Contact *contact);
+time_t e_m365_contact_get_created_date_time (EM365Contact *contact);
+time_t e_m365_contact_get_last_modified_date_time
+ (EM365Contact *contact);
+const gchar * e_m365_contact_get_assistant_name (EM365Contact *contact);
+void e_m365_contact_add_assistant_name (JsonBuilder *builder,
+ const gchar *value);
+time_t e_m365_contact_get_birthday (EM365Contact *contact);
+void e_m365_contact_add_birthday (JsonBuilder *builder,
+ time_t value);
+EM365PhysicalAddress *
+ e_m365_contact_get_business_address (EM365Contact *contact);
+void e_m365_contact_add_business_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street);
+const gchar * e_m365_contact_get_business_home_page (EM365Contact *contact);
+void e_m365_contact_add_business_home_page (JsonBuilder *builder,
+ const gchar *value);
+JsonArray * e_m365_contact_get_business_phones (EM365Contact *contact); /* const gchar * */
+void e_m365_contact_begin_business_phones (JsonBuilder *builder);
+void e_m365_contact_end_business_phones (JsonBuilder *builder);
+void e_m365_contact_add_business_phone (JsonBuilder *builder,
+ const gchar *value);
+JsonArray * e_m365_contact_get_categories (EM365Contact *contact); /* const gchar * */
+void e_m365_contact_begin_categories (JsonBuilder *builder);
+void e_m365_contact_end_categories (JsonBuilder *builder);
+void e_m365_contact_add_category (JsonBuilder *builder,
+ const gchar *category);
+JsonArray * e_m365_contact_get_children (EM365Contact *contact); /* const gchar * */
+void e_m365_contact_begin_children (JsonBuilder *builder);
+void e_m365_contact_end_children (JsonBuilder *builder);
+void e_m365_contact_add_child (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_company_name (EM365Contact *contact);
+void e_m365_contact_add_company_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_department (EM365Contact *contact);
+void e_m365_contact_add_department (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_display_name (EM365Contact *contact);
+void e_m365_contact_add_display_name (JsonBuilder *builder,
+ const gchar *value);
+JsonArray * e_m365_contact_get_email_addresses (EM365Contact *contact); /* EM365EmailAddress * */
+void e_m365_contact_begin_email_addresses (JsonBuilder *builder);
+void e_m365_contact_end_email_addresses (JsonBuilder *builder);
+const gchar * e_m365_contact_get_file_as (EM365Contact *contact);
+void e_m365_contact_add_file_as (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_generation (EM365Contact *contact);
+void e_m365_contact_add_generation (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_given_name (EM365Contact *contact);
+void e_m365_contact_add_given_name (JsonBuilder *builder,
+ const gchar *value);
+EM365PhysicalAddress *
+ e_m365_contact_get_home_address (EM365Contact *contact);
+void e_m365_contact_add_home_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street);
+JsonArray * e_m365_contact_get_home_phones (EM365Contact *contact); /* const gchar * */
+void e_m365_contact_begin_home_phones (JsonBuilder *builder);
+void e_m365_contact_end_home_phones (JsonBuilder *builder);
+void e_m365_contact_add_home_phone (JsonBuilder *builder,
+ const gchar *value);
+JsonArray * e_m365_contact_get_im_addresses (EM365Contact *contact); /* const gchar * */
+void e_m365_contact_begin_im_addresses (JsonBuilder *builder);
+void e_m365_contact_end_im_addresses (JsonBuilder *builder);
+void e_m365_contact_add_im_address (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_initials (EM365Contact *contact);
+void e_m365_contact_add_initials (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_job_title (EM365Contact *contact);
+void e_m365_contact_add_job_title (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_manager (EM365Contact *contact);
+void e_m365_contact_add_manager (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_middle_name (EM365Contact *contact);
+void e_m365_contact_add_middle_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_mobile_phone (EM365Contact *contact);
+void e_m365_contact_add_mobile_phone (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_nick_name (EM365Contact *contact);
+void e_m365_contact_add_nick_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_office_location (EM365Contact *contact);
+void e_m365_contact_add_office_location (JsonBuilder *builder,
+ const gchar *value);
+EM365PhysicalAddress *
+ e_m365_contact_get_other_address (EM365Contact *contact);
+void e_m365_contact_add_other_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street);
+const gchar * e_m365_contact_get_personal_notes (EM365Contact *contact);
+void e_m365_contact_add_personal_notes (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_profession (EM365Contact *contact);
+void e_m365_contact_add_profession (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_spouse_name (EM365Contact *contact);
+void e_m365_contact_add_spouse_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_surname (EM365Contact *contact);
+void e_m365_contact_add_surname (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_title (EM365Contact *contact);
+void e_m365_contact_add_title (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_yomi_company_name (EM365Contact *contact);
+void e_m365_contact_add_yomi_company_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_yomi_given_name (EM365Contact *contact);
+void e_m365_contact_add_yomi_given_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_contact_get_yomi_surname (EM365Contact *contact);
+void e_m365_contact_add_yomi_surname (JsonBuilder *builder,
+ const gchar *value);
+
+const gchar * e_m365_calendar_group_get_id (EM365CalendarGroup *group);
+const gchar * e_m365_calendar_group_get_change_key (EM365CalendarGroup *group);
+const gchar * e_m365_calendar_group_get_class_id (EM365CalendarGroup *group);
+const gchar * e_m365_calendar_group_get_name (EM365CalendarGroup *group);
+
+const gchar * e_m365_calendar_get_id (EM365Calendar *calendar);
+const gchar * e_m365_calendar_get_change_key (EM365Calendar *calendar);
+gboolean e_m365_calendar_get_can_edit (EM365Calendar *calendar);
+gboolean e_m365_calendar_get_can_share (EM365Calendar *calendar);
+gboolean e_m365_calendar_get_can_view_private_items
+ (EM365Calendar *calendar);
+gboolean e_m365_calendar_get_is_removable (EM365Calendar *calendar);
+gboolean e_m365_calendar_get_is_tallying_responses
+ (EM365Calendar *calendar);
+EM365EmailAddress *
+ e_m365_calendar_get_owner (EM365Calendar *calendar);
+const gchar * e_m365_calendar_get_name (EM365Calendar *calendar);
+void e_m365_calendar_add_name (JsonBuilder *builder,
+ const gchar *name);
+guint32 e_m365_calendar_get_allowed_online_meeting_providers /* bit-or of
EM365OnlineMeetingProviderType */
+ (EM365Calendar *calendar);
+void e_m365_calendar_add_allowed_online_meeting_providers
+ (JsonBuilder *builder,
+ guint providers); /* bit-or of
EM365OnlineMeetingProviderType */
+EM365CalendarColorType
+ e_m365_calendar_get_color (EM365Calendar *calendar);
+void e_m365_calendar_add_color (JsonBuilder *builder,
+ EM365CalendarColorType color);
+EM365OnlineMeetingProviderType
+ e_m365_calendar_get_default_online_meeting_provider
+ (EM365Calendar *calendar);
+void e_m365_calendar_add_default_online_meeting_provider
+ (JsonBuilder *builder,
+ EM365OnlineMeetingProviderType provider);
+EM365ResponseType
+ e_m365_response_status_get_response (EM365ResponseStatus *response_status);
+time_t e_m365_response_status_get_time (EM365ResponseStatus *response_status);
+
+EM365ResponseStatus *
+ e_m365_attendee_get_status (EM365Attendee *attendee);
+EM365AttendeeType
+ e_m365_attendee_get_type (EM365Attendee *attendee);
+EM365EmailAddress *
+ e_m365_attendee_get_email_address (EM365Attendee *attendee);
+
+gdouble e_m365_outlook_geo_coordinates_get_accuracy
+ (EM365OutlookGeoCoordinates *coords);
+void e_m365_outlook_geo_coordinates_add_accuracy
+ (JsonBuilder *builder,
+ gdouble value);
+gdouble e_m365_outlook_geo_coordinates_get_altitude
+ (EM365OutlookGeoCoordinates *coords);
+void e_m365_outlook_geo_coordinates_add_altitude
+ (JsonBuilder *builder,
+ gdouble value);
+gdouble e_m365_outlook_geo_coordinates_get_altitude_accuracy
+ (EM365OutlookGeoCoordinates *coords);
+void e_m365_outlook_geo_coordinates_add_altitude_accuracy
+ (JsonBuilder *builder,
+ gdouble value);
+gdouble e_m365_outlook_geo_coordinates_get_latitude
+ (EM365OutlookGeoCoordinates *coords);
+void e_m365_outlook_geo_coordinates_add_latitude
+ (JsonBuilder *builder,
+ gdouble value);
+gdouble e_m365_outlook_geo_coordinates_get_longitude
+ (EM365OutlookGeoCoordinates *coords);
+void e_m365_outlook_geo_coordinates_add_longitude
+ (JsonBuilder *builder,
+ gdouble value);
+EM365PhysicalAddress *
+ e_m365_location_get_address (EM365Location *location);
+void e_m365_location_add_address (JsonBuilder *builder,
+ const gchar *city,
+ const gchar *country_or_region,
+ const gchar *postal_code,
+ const gchar *state,
+ const gchar *street);
+EM365OutlookGeoCoordinates *
+ e_m365_location_get_coordinates (EM365Location *location);
+void e_m365_location_begin_coordinates (JsonBuilder *builder);
+void e_m365_location_end_coordinates (JsonBuilder *builder);
+const gchar * e_m365_location_get_display_name (EM365Location *location);
+void e_m365_location_add_display_name (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_location_get_email_address (EM365Location *location);
+void e_m365_location_add_email_address (JsonBuilder *builder,
+ const gchar *value);
+const gchar * e_m365_location_get_uri (EM365Location *location);
+void e_m365_location_add_uri (JsonBuilder *builder,
+ const gchar *value);
+EM365LocationType
+ e_m365_location_get_type (EM365Location *location);
+
+const gchar * e_m365_phone_get_number (EM365Phone *phone);
+EM365PhoneType e_m365_phone_get_type (EM365Phone *phone);
+
+const gchar * e_m365_online_meeting_info_get_conference_id
+ (EM365OnlineMeetingInfo *meeting_info);
+const gchar * e_m365_online_meeting_info_get_join_url (EM365OnlineMeetingInfo *meeting_info);
+JsonArray * e_m365_online_meeting_info_get_phones (EM365OnlineMeetingInfo *meeting_info); /* EM365Phone
* */
+const gchar * e_m365_online_meeting_info_get_quick_dial
+ (EM365OnlineMeetingInfo *meeting_info);
+JsonArray * e_m365_online_meeting_info_get_toll_free_numbers
+ (EM365OnlineMeetingInfo *meeting_info); /* gchar * */
+const gchar * e_m365_online_meeting_info_get_toll_number
+ (EM365OnlineMeetingInfo *meeting_info);
+
+EM365DayOfWeekType
+ e_m365_array_get_day_of_week_element (JsonArray *array, /* const gchar * representing
EM365DayOfWeekType */
+ guint index);
+gint e_m365_recurrence_pattern_get_day_of_month
+ (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_day_of_month
+ (JsonBuilder *builder,
+ gint value);
+JsonArray * e_m365_recurrence_pattern_get_days_of_week
+ (EM365RecurrencePattern *pattern); /* const gchar *
representing EM365DayOfWeekType, use e_m365_array_get_day_of_week_element() */
+void e_m365_recurrence_pattern_begin_days_of_week
+ (JsonBuilder *builder);
+void e_m365_recurrence_pattern_end_days_of_week
+ (JsonBuilder *builder);
+void e_m365_recurrence_pattern_add_day_of_week
+ (JsonBuilder *builder,
+ EM365DayOfWeekType value);
+EM365DayOfWeekType
+ e_m365_recurrence_pattern_get_first_day_of_week
+ (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_first_day_of_week
+ (JsonBuilder *builder,
+ EM365DayOfWeekType value);
+EM365WeekIndexType
+ e_m365_recurrence_pattern_get_index (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_index (JsonBuilder *builder,
+ EM365WeekIndexType value);
+gint e_m365_recurrence_pattern_get_interval (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_interval (JsonBuilder *builder,
+ gint value);
+gint e_m365_recurrence_pattern_get_month (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_month (JsonBuilder *builder,
+ gint value);
+EM365RecurrencePatternType
+ e_m365_recurrence_pattern_get_type (EM365RecurrencePattern *pattern);
+void e_m365_recurrence_pattern_add_type (JsonBuilder *builder,
+ EM365RecurrencePatternType value);
+
+EM365Date e_m365_recurrence_range_get_end_date (EM365RecurrenceRange *range);
+void e_m365_recurrence_range_add_end_date (JsonBuilder *builder,
+ EM365Date value);
+gint e_m365_recurrence_range_get_number_of_occurrences
+ (EM365RecurrenceRange *range);
+void e_m365_recurrence_range_add_number_of_occurrences
+ (JsonBuilder *builder,
+ gint value);
+const gchar * e_m365_recurrence_range_get_recurrence_time_zone
+ (EM365RecurrenceRange *range);
+void e_m365_recurrence_range_add_recurrence_time_zone
+ (JsonBuilder *builder,
+ const gchar *value);
+EM365Date e_m365_recurrence_range_get_start_date (EM365RecurrenceRange *range);
+void e_m365_recurrence_range_add_start_date (JsonBuilder *builder,
+ EM365Date value);
+EM365RecurrenceRangeType
+ e_m365_recurrence_range_get_type (EM365RecurrenceRange *range);
+void e_m365_recurrence_range_add_type (JsonBuilder *builder,
+ EM365RecurrenceRangeType value);
+
+EM365RecurrencePattern *
+ e_m365_patterned_recurrence_get_pattern (EM365PatternedRecurrence *patterned_recurrence);
+void e_m365_patterned_recurrence_begin_pattern
+ (JsonBuilder *builder);
+void e_m365_patterned_recurrence_end_pattern
+ (JsonBuilder *builder);
+EM365RecurrenceRange *
+ e_m365_patterned_recurrence_get_range (EM365PatternedRecurrence *patterned_recurrence);
+void e_m365_patterned_recurrence_begin_range
+ (JsonBuilder *builder);
+void e_m365_patterned_recurrence_end_range
+ (JsonBuilder *builder);
+
+const gchar * e_m365_event_get_id (EM365Event *event);
+const gchar * e_m365_event_get_change_key (EM365Event *event);
+JsonArray * e_m365_event_get_attendees (EM365Event *event); /* EM365Attendee * */
+void e_m365_event_begin_attendees (JsonBuilder *builder);
+void e_m365_event_end_attendees (JsonBuilder *builder);
+void e_m365_event_add_attendee (JsonBuilder *builder,
+ EM365AttendeeType type,
+ EM365ResponseType response,
+ time_t response_time,
+ const gchar *name,
+ const gchar *address);
+void e_m365_event_add_null_attendees (JsonBuilder *builder);
+EM365ItemBody * e_m365_event_get_body (EM365Event *event);
+void e_m365_event_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content);
+const gchar * e_m365_event_get_body_preview (EM365Event *event);
+JsonArray * e_m365_event_get_categories (EM365Event *event); /* const gchar * */
+void e_m365_event_begin_categories (JsonBuilder *builder);
+void e_m365_event_end_categories (JsonBuilder *builder);
+void e_m365_event_add_category (JsonBuilder *builder,
+ const gchar *category);
+time_t e_m365_event_get_created_date_time (EM365Event *event);
+EM365DateTimeWithZone *
+ e_m365_event_get_end (EM365Event *event);
+void e_m365_event_add_end (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+gboolean e_m365_event_get_has_attachments (EM365Event *event);
+const gchar * e_m365_event_get_ical_uid (EM365Event *event);
+EM365ImportanceType
+ e_m365_event_get_importance (EM365Event *event);
+void e_m365_event_add_importance (JsonBuilder *builder,
+ EM365ImportanceType value);
+gboolean e_m365_event_get_is_all_day (EM365Event *event);
+void e_m365_event_add_is_all_day (JsonBuilder *builder,
+ gboolean value);
+gboolean e_m365_event_get_is_cancelled (EM365Event *event);
+gboolean e_m365_event_get_is_online_meeting (EM365Event *event);
+void e_m365_event_add_is_online_meeting (JsonBuilder *builder,
+ gboolean value);
+gboolean e_m365_event_get_is_organizer (EM365Event *event);
+gboolean e_m365_event_get_is_reminder_on (EM365Event *event);
+void e_m365_event_add_is_reminder_on (JsonBuilder *builder,
+ gboolean value);
+time_t e_m365_event_get_last_modified_date_time(EM365Event *event);
+EM365Location * e_m365_event_get_location (EM365Event *event);
+void e_m365_event_begin_location (JsonBuilder *builder);
+void e_m365_event_end_location (JsonBuilder *builder);
+void e_m365_event_add_null_location (JsonBuilder *builder);
+JsonArray * e_m365_event_get_locations (EM365Event *event); /* EM365Location * */
+void e_m365_event_begin_locations (JsonBuilder *builder);
+void e_m365_event_end_locations (JsonBuilder *builder);
+void e_m365_event_begin_locations_location (JsonBuilder *builder);
+void e_m365_event_end_locations_location (JsonBuilder *builder);
+EM365OnlineMeetingInfo *
+ e_m365_event_get_online_meeting_info (EM365Event *event);
+EM365OnlineMeetingProviderType
+ e_m365_event_get_online_meeting_provider(EM365Event *event);
+void e_m365_event_add_online_meeting_provider(JsonBuilder *builder,
+ EM365OnlineMeetingProviderType value);
+const gchar * e_m365_event_get_online_meeting_url (EM365Event *event);
+EM365Recipient *e_m365_event_get_organizer (EM365Event *event);
+void e_m365_event_add_organizer (JsonBuilder *builder,
+ const gchar *name,
+ const gchar *address);
+void e_m365_event_add_null_organizer (JsonBuilder *builder);
+const gchar * e_m365_event_get_original_end_timezone (EM365Event *event);
+time_t e_m365_event_get_original_start (EM365Event *event);
+const gchar * e_m365_event_get_original_start_timezone(EM365Event *event);
+EM365PatternedRecurrence *
+ e_m365_event_get_recurrence (EM365Event *event);
+void e_m365_event_begin_recurrence (JsonBuilder *builder);
+void e_m365_event_end_recurrence (JsonBuilder *builder);
+void e_m365_event_add_null_recurrence (JsonBuilder *builder);
+gint e_m365_event_get_reminder_minutes_before_start
+ (EM365Event *event);
+void e_m365_event_add_reminder_minutes_before_start
+ (JsonBuilder *builder,
+ gint value);
+gboolean e_m365_event_get_response_requested (EM365Event *event);
+void e_m365_event_add_response_requested (JsonBuilder *builder,
+ gboolean value);
+EM365ResponseStatus *
+ e_m365_event_get_response_status (EM365Event *event);
+EM365SensitivityType
+ e_m365_event_get_sensitivity (EM365Event *event);
+void e_m365_event_add_sensitivity (JsonBuilder *builder,
+ EM365SensitivityType value);
+const gchar * e_m365_event_get_series_master_id (EM365Event *event);
+EM365FreeBusyStatusType
+ e_m365_event_get_show_as (EM365Event *event);
+void e_m365_event_add_show_as (JsonBuilder *builder,
+ EM365FreeBusyStatusType value);
+EM365DateTimeWithZone *
+ e_m365_event_get_start (EM365Event *event);
+void e_m365_event_add_start (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+const gchar * e_m365_event_get_subject (EM365Event *event);
+void e_m365_event_add_subject (JsonBuilder *builder,
+ const gchar *value);
+EM365EventTypeType
+ e_m365_event_get_type (EM365Event *event);
+const gchar * e_m365_event_get_web_link (EM365Event *event);
+
+const gchar * e_m365_free_busy_error_get_message (EM365FreeBusyError *fberror);
+const gchar * e_m365_free_busy_error_get_response_code(EM365FreeBusyError *fberror);
+
+EM365DateTimeWithZone *
+ e_m365_schedule_item_get_end (EM365ScheduleItem *schitem);
+gboolean e_m365_schedule_item_get_is_private (EM365ScheduleItem *schitem);
+const gchar * e_m365_schedule_item_get_location (EM365ScheduleItem *schitem);
+EM365DateTimeWithZone *
+ e_m365_schedule_item_get_start (EM365ScheduleItem *schitem);
+EM365FreeBusyStatusType
+ e_m365_schedule_item_get_status (EM365ScheduleItem *schitem);
+const gchar * e_m365_schedule_item_get_subject (EM365ScheduleItem *schitem);
+
+JsonArray * e_m365_working_hours_get_days_of_week (EM365WorkingHours *wrkhrs); /* Use
e_m365_array_get_day_of_week_element() to get the items */
+EM365TimeOfDay e_m365_working_hours_get_start_time (EM365WorkingHours *wrkhrs);
+EM365TimeOfDay e_m365_working_hours_get_end_time (EM365WorkingHours *wrkhrs);
+const gchar * e_m365_working_hours_get_time_zone_name (EM365WorkingHours *wrkhrs);
+
+const gchar * e_m365_schedule_information_get_availability_view
+ (EM365ScheduleInformation *schinfo);
+EM365FreeBusyError *
+ e_m365_schedule_information_get_free_busy_error
+ (EM365ScheduleInformation *schinfo);
+const gchar * e_m365_schedule_information_get_schedule_id
+ (EM365ScheduleInformation *schinfo);
+JsonArray * e_m365_schedule_information_get_schedule_items /* EM365ScheduleItem * */
+ (EM365ScheduleInformation *schinfo);
+EM365WorkingHours *
+ e_m365_schedule_information_get_working_hours
+ (EM365ScheduleInformation *schinfo);
+
+const gchar * e_m365_task_folder_get_id (EM365TaskFolder *folder);
+const gchar * e_m365_task_folder_get_change_key (EM365TaskFolder *folder);
+const gchar * e_m365_task_folder_get_parent_group_key (EM365TaskFolder *folder);
+const gchar * e_m365_task_folder_get_name (EM365TaskFolder *folder);
+gboolean e_m365_task_folder_get_is_default_folder(EM365TaskFolder *folder);
+
+const gchar * e_m365_task_group_get_id (EM365TaskGroup *group);
+const gchar * e_m365_task_group_get_change_key (EM365TaskGroup *group);
+const gchar * e_m365_task_group_get_group_key (EM365TaskGroup *group);
+const gchar * e_m365_task_group_get_name (EM365TaskGroup *group);
+gboolean e_m365_task_group_get_is_default_group (EM365TaskGroup *group);
+
+const gchar * e_m365_task_get_id (EM365Task *task);
+const gchar * e_m365_task_get_change_key (EM365Task *task);
+const gchar * e_m365_task_get_parent_folder_id (EM365Task *task);
+const gchar * e_m365_task_get_assigned_to (EM365Task *task);
+EM365ItemBody * e_m365_task_get_body (EM365Task *task);
+void e_m365_task_add_body (JsonBuilder *builder,
+ EM365ItemBodyContentTypeType content_type,
+ const gchar *content);
+JsonArray * e_m365_task_get_categories (EM365Task *task); /* const gchar * */
+void e_m365_task_begin_categories (JsonBuilder *builder);
+void e_m365_task_end_categories (JsonBuilder *builder);
+void e_m365_task_add_category (JsonBuilder *builder,
+ const gchar *category);
+EM365DateTimeWithZone *
+ e_m365_task_get_completed_date_time (EM365Task *task);
+void e_m365_task_add_completed_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+time_t e_m365_task_get_created_date_time (EM365Task *task);
+EM365DateTimeWithZone *
+ e_m365_task_get_due_date_time (EM365Task *task);
+void e_m365_task_add_due_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+gboolean e_m365_task_get_has_attachments (EM365Task *task);
+EM365ImportanceType
+ e_m365_task_get_importance (EM365Task *task);
+void e_m365_task_add_importance (JsonBuilder *builder,
+ EM365ImportanceType value);
+gboolean e_m365_task_get_is_reminder_on (EM365Task *task);
+void e_m365_task_add_is_reminder_on (JsonBuilder *builder,
+ gboolean value);
+time_t e_m365_task_get_last_modified_date_time (EM365Task *task);
+const gchar * e_m365_task_get_owner (EM365Task *task);
+void e_m365_task_add_owner (JsonBuilder *builder,
+ const gchar *value);
+EM365PatternedRecurrence *
+ e_m365_task_get_recurrence (EM365Task *task);
+void e_m365_task_begin_recurrence (JsonBuilder *builder);
+void e_m365_task_end_recurrence (JsonBuilder *builder);
+void e_m365_task_add_null_recurrence (JsonBuilder *builder);
+EM365DateTimeWithZone *
+ e_m365_task_get_reminder_date_time (EM365Task *task);
+void e_m365_task_add_reminder_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+EM365SensitivityType
+ e_m365_task_get_sensitivity (EM365Task *task);
+void e_m365_task_add_sensitivity (JsonBuilder *builder,
+ EM365SensitivityType value);
+EM365DateTimeWithZone *
+ e_m365_task_get_start_date_time (EM365Task *task);
+void e_m365_task_add_start_date_time (JsonBuilder *builder,
+ time_t date_time,
+ const gchar *zone);
+EM365StatusType e_m365_task_get_status (EM365Task *task);
+void e_m365_task_add_status (JsonBuilder *builder,
+ EM365StatusType value);
+const gchar * e_m365_task_get_subject (EM365Task *task);
+void e_m365_task_add_subject (JsonBuilder *builder,
+ const gchar *value);
+
+G_END_DECLS
+
+#endif /* E_M365_JSON_UTILS_H */
diff --git a/src/Microsoft365/common/e-m365-tz-utils.c b/src/Microsoft365/common/e-m365-tz-utils.c
new file mode 100644
index 00000000..bc0cf6bd
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-tz-utils.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include "e-m365-tz-utils.h"
+
+/*
+ * A bunch of global variables used to map the ICalTimezone to MSDN[0] format.
+ * Also, some auxiliar functions to translate from one tz type to another.
+ *
+ * [0]: http://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx
+ */
+static GRecMutex tz_mutex;
+static GHashTable *ical_to_msdn = NULL;
+static GHashTable *msdn_to_ical = NULL;
+static guint tables_counter = 0;
+
+void
+e_m365_tz_utils_ref_windows_zones (void)
+{
+ const gchar *xpath_eval_exp;
+ gchar *filename = NULL;
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpath_ctxt;
+ xmlXPathObjectPtr xpath_obj;
+ xmlNodeSetPtr nodes;
+ gint i, len;
+
+ g_rec_mutex_lock (&tz_mutex);
+ if (ical_to_msdn != NULL && msdn_to_ical != NULL) {
+ g_hash_table_ref (ical_to_msdn);
+ g_hash_table_ref (msdn_to_ical);
+ tables_counter++;
+
+ g_rec_mutex_unlock (&tz_mutex);
+ return;
+ }
+
+ filename = g_build_filename (M365_DATADIR, "windowsZones.xml", NULL);
+
+ doc = xmlReadFile (filename, NULL, 0);
+
+ if (doc == NULL) {
+ g_warning (G_STRLOC "Could not map %s file.", filename);
+ g_free (filename);
+
+ g_rec_mutex_unlock (&tz_mutex);
+ return;
+ }
+
+ xpath_eval_exp = "/supplementalData/windowsZones/mapTimezones/mapZone";
+
+ xpath_ctxt = xmlXPathNewContext (doc);
+ xpath_obj = xmlXPathEvalExpression (BAD_CAST xpath_eval_exp, xpath_ctxt);
+
+ if (xpath_obj == NULL) {
+ g_warning (G_STRLOC "Unable to evaluate xpath expression \"%s\".", xpath_eval_exp);
+ xmlXPathFreeContext (xpath_ctxt);
+ xmlFreeDoc (doc);
+ g_free (filename);
+
+ g_rec_mutex_unlock (&tz_mutex);
+ return;
+ }
+
+ nodes = xpath_obj->nodesetval;
+ len = nodes->nodeNr;
+
+ msdn_to_ical = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ ical_to_msdn = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ tables_counter++;
+
+ for (i = 0; i < len; i++) {
+ xmlChar *msdn = xmlGetProp (nodes->nodeTab[i], BAD_CAST "other");
+ xmlChar *ical = xmlGetProp (nodes->nodeTab[i], BAD_CAST "type");
+ gchar **tokens;
+ gint tokens_len;
+
+ tokens = g_strsplit ((gchar *) ical, " ", 0);
+ tokens_len = g_strv_length (tokens);
+ if (tokens_len == 1) {
+ if (!g_hash_table_lookup (msdn_to_ical, msdn))
+ g_hash_table_insert (msdn_to_ical, g_strdup ((gchar *) msdn), g_strdup
((gchar *) ical));
+
+ if (!g_hash_table_lookup (ical_to_msdn, ical))
+ g_hash_table_insert (ical_to_msdn, g_strdup ((gchar *) ical), g_strdup
((gchar *) msdn));
+ } else {
+ gint j;
+ for (j = 0; j < tokens_len; j++) {
+ if (!g_hash_table_lookup (msdn_to_ical, msdn))
+ g_hash_table_insert (msdn_to_ical, g_strdup ((gchar *) msdn),
g_strdup (tokens[j]));
+
+ if (!g_hash_table_lookup (ical_to_msdn, tokens[j]))
+ g_hash_table_insert (ical_to_msdn, g_strdup (tokens[j]), g_strdup
((gchar *) msdn));
+ }
+ }
+
+ g_strfreev (tokens);
+ xmlFree (ical);
+ xmlFree (msdn);
+ }
+
+ xmlXPathFreeObject (xpath_obj);
+ xmlXPathFreeContext (xpath_ctxt);
+ xmlFreeDoc (doc);
+ g_free (filename);
+
+ g_rec_mutex_unlock (&tz_mutex);
+}
+
+void
+e_m365_tz_utils_unref_windows_zones (void)
+{
+ g_rec_mutex_lock (&tz_mutex);
+ if (ical_to_msdn != NULL)
+ g_hash_table_unref (ical_to_msdn);
+
+ if (msdn_to_ical != NULL)
+ g_hash_table_unref (msdn_to_ical);
+
+ if (tables_counter > 0) {
+ tables_counter--;
+
+ if (tables_counter == 0) {
+ ical_to_msdn = NULL;
+ msdn_to_ical = NULL;
+ }
+ }
+
+ g_rec_mutex_unlock (&tz_mutex);
+}
+
+const gchar *
+e_m365_tz_utils_get_msdn_equivalent (const gchar *ical_tz_location)
+{
+ const gchar *msdn_tz_location = NULL;
+
+ if (!ical_tz_location || !*ical_tz_location)
+ return NULL;
+
+ g_rec_mutex_lock (&tz_mutex);
+ if (ical_to_msdn == NULL) {
+ g_rec_mutex_unlock (&tz_mutex);
+
+ g_warn_if_reached ();
+ return NULL;
+ }
+
+ msdn_tz_location = g_hash_table_lookup (ical_to_msdn, ical_tz_location);
+ g_rec_mutex_unlock (&tz_mutex);
+
+ return msdn_tz_location;
+}
+
+const gchar *
+e_m365_tz_utils_get_ical_equivalent (const gchar *msdn_tz_location)
+{
+ const gchar *ical_tz_location = NULL;
+
+ if (!msdn_tz_location || !*msdn_tz_location)
+ return NULL;
+
+ g_rec_mutex_lock (&tz_mutex);
+ if (msdn_to_ical == NULL) {
+ g_rec_mutex_unlock (&tz_mutex);
+
+ g_warn_if_reached ();
+ return NULL;
+ }
+
+ ical_tz_location = g_hash_table_lookup (msdn_to_ical, msdn_tz_location);
+ g_rec_mutex_unlock (&tz_mutex);
+
+ return ical_tz_location;
+}
diff --git a/src/Microsoft365/common/e-m365-tz-utils.h b/src/Microsoft365/common/e-m365-tz-utils.h
new file mode 100644
index 00000000..0aa6002c
--- /dev/null
+++ b/src/Microsoft365/common/e-m365-tz-utils.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_TZ_UTILS_H
+#define E_M365_TZ_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void e_m365_tz_utils_ref_windows_zones (void);
+void e_m365_tz_utils_unref_windows_zones (void);
+const gchar * e_m365_tz_utils_get_msdn_equivalent (const gchar *ical_tz_location);
+const gchar * e_m365_tz_utils_get_ical_equivalent (const gchar *msdn_tz_location);
+
+G_END_DECLS
+
+#endif /* E_M365_TZ_UTILS_H */
diff --git a/src/Microsoft365/common/e-oauth2-service-microsoft365.c
b/src/Microsoft365/common/e-oauth2-service-microsoft365.c
new file mode 100644
index 00000000..18c12f17
--- /dev/null
+++ b/src/Microsoft365/common/e-oauth2-service-microsoft365.c
@@ -0,0 +1,374 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+#include <libedataserver/libedataserver.h>
+
+#include "camel-m365-settings.h"
+
+#include "e-oauth2-service-microsoft365.h"
+
+/* https://portal.azure.com/
+ https://docs.microsoft.com/en-us/graph/auth/
+
+ https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-developers-guide
+ https://tsmatz.wordpress.com/2016/10/07/application-permission-with-v2-endpoint-and-microsoft-graph/
+*/
+
+#define MICROSOFT365_SCOPE "Calendars.ReadWrite " \
+ "Calendars.ReadWrite.Shared " \
+ "Contacts.ReadWrite " \
+ "Contacts.ReadWrite.Shared " \
+ "Mail.ReadWrite " \
+ "Mail.ReadWrite.Shared " \
+ "Mail.Send " \
+ "Mail.Send.Shared " \
+ "MailboxSettings.Read " \
+ /*"Notes.Create "*/ \
+ /*"Notes.ReadWrite.All "*/ \
+ "offline_access " \
+ "Tasks.ReadWrite " \
+ "Tasks.ReadWrite.Shared "
+
+struct _EOAuth2ServiceMicrosoft365Private
+{
+ GMutex string_cache_lock;
+ GHashTable *string_cache;
+};
+
+/* Forward Declarations */
+static void e_oauth2_service_microsoft365_oauth2_service_init (EOAuth2ServiceInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (EOAuth2ServiceMicrosoft365, e_oauth2_service_microsoft365,
E_TYPE_OAUTH2_SERVICE_BASE, 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (E_TYPE_OAUTH2_SERVICE,
e_oauth2_service_microsoft365_oauth2_service_init)
+ G_ADD_PRIVATE_DYNAMIC (EOAuth2ServiceMicrosoft365))
+
+static const gchar *
+eos_microsoft365_cache_string (EOAuth2ServiceMicrosoft365 *oauth2_microsoft365,
+ gchar *str) /* takes ownership of the 'str' */
+{
+ const gchar *cached_str;
+
+ g_return_val_if_fail (E_IS_OAUTH2_SERVICE_MICROSOFT365 (oauth2_microsoft365), NULL);
+
+ if (!str)
+ return NULL;
+
+ if (!*str)
+ return "";
+
+ g_mutex_lock (&oauth2_microsoft365->priv->string_cache_lock);
+
+ cached_str = g_hash_table_lookup (oauth2_microsoft365->priv->string_cache, str);
+ if (cached_str) {
+ g_free (str);
+ } else {
+ g_hash_table_insert (oauth2_microsoft365->priv->string_cache, str, str);
+ cached_str = str;
+ }
+
+ g_mutex_unlock (&oauth2_microsoft365->priv->string_cache_lock);
+
+ return cached_str;
+}
+
+static CamelM365Settings *
+eos_microsoft365_get_camel_settings (ESource *source)
+{
+ ESourceCamel *extension;
+
+ if (!source)
+ return NULL;
+
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+ extension = e_source_get_extension (source, e_source_camel_get_extension_name ("microsoft365"));
+
+ return CAMEL_M365_SETTINGS (e_source_camel_get_settings (extension));
+}
+
+static gboolean
+eos_microsoft365_guess_can_process (EOAuth2Service *service,
+ const gchar *protocol,
+ const gchar *hostname)
+{
+ return e_oauth2_services_is_supported () &&
+ protocol && g_ascii_strcasecmp (protocol, "microsoft365") == 0;
+}
+
+static const gchar *
+eos_microsoft365_get_name (EOAuth2Service *service)
+{
+ return "Microsoft365";
+}
+
+static const gchar *
+eos_microsoft365_get_display_name (EOAuth2Service *service)
+{
+ /* Translators: This is a user-visible string, display name of an OAuth2 service. */
+ return C_("OAuth2Service", "Microsoft365");
+}
+
+static const gchar *
+eos_microsoft365_get_client_id (EOAuth2Service *service,
+ ESource *source)
+{
+ EOAuth2ServiceMicrosoft365 *oauth2_microsoft365 = E_OAUTH2_SERVICE_MICROSOFT365 (service);
+ CamelM365Settings *m365_settings;
+
+ m365_settings = eos_microsoft365_get_camel_settings (source);
+ if (m365_settings && camel_m365_settings_get_override_oauth2 (m365_settings)) {
+ gchar *client_id = camel_m365_settings_dup_oauth2_client_id (m365_settings);
+
+ if (client_id && !*client_id) {
+ g_free (client_id);
+ client_id = NULL;
+ }
+
+ if (client_id)
+ return eos_microsoft365_cache_string (oauth2_microsoft365, client_id);
+ }
+
+ return MICROSOFT365_CLIENT_ID;
+}
+
+static const gchar *
+eos_microsoft365_get_client_secret (EOAuth2Service *service,
+ ESource *source)
+{
+ return NULL;
+}
+
+static const gchar *
+eos_microsoft365_get_authentication_uri (EOAuth2Service *service,
+ ESource *source)
+{
+ EOAuth2ServiceMicrosoft365 *oauth2_microsoft365 = E_OAUTH2_SERVICE_MICROSOFT365 (service);
+ CamelM365Settings *m365_settings;
+
+ m365_settings = eos_microsoft365_get_camel_settings (source);
+ if (m365_settings && camel_m365_settings_get_override_oauth2 (m365_settings)) {
+ gchar *tenant;
+ const gchar *res;
+
+ tenant = camel_m365_settings_dup_oauth2_tenant (m365_settings);
+ if (tenant && !*tenant) {
+ g_free (tenant);
+ tenant = NULL;
+ }
+
+ res = eos_microsoft365_cache_string (oauth2_microsoft365,
+ g_strdup_printf ("https://login.microsoftonline.com/%s/oauth2/v2.0/authorize",
+ tenant ? tenant : MICROSOFT365_TENANT));
+
+ g_free (tenant);
+
+ return res;
+ }
+
+ return "https://login.microsoftonline.com/" MICROSOFT365_TENANT "/oauth2/v2.0/authorize";
+}
+
+static const gchar *
+eos_microsoft365_get_refresh_uri (EOAuth2Service *service,
+ ESource *source)
+{
+ EOAuth2ServiceMicrosoft365 *oauth2_microsoft365 = E_OAUTH2_SERVICE_MICROSOFT365 (service);
+ CamelM365Settings *m365_settings;
+
+ m365_settings = eos_microsoft365_get_camel_settings (source);
+ if (m365_settings && camel_m365_settings_get_override_oauth2 (m365_settings)) {
+ gchar *tenant;
+ const gchar *res;
+
+ tenant = camel_m365_settings_dup_oauth2_tenant (m365_settings);
+ if (tenant && !*tenant) {
+ g_free (tenant);
+ tenant = NULL;
+ }
+
+ res = eos_microsoft365_cache_string (oauth2_microsoft365,
+ g_strdup_printf ("https://login.microsoftonline.com/%s/oauth2/v2.0/token",
+ tenant ? tenant : MICROSOFT365_TENANT));
+
+ g_free (tenant);
+
+ return res;
+ }
+
+ return "https://login.microsoftonline.com/" MICROSOFT365_TENANT "/oauth2/v2.0/token";
+}
+
+static const gchar *
+eos_microsoft365_get_redirect_uri (EOAuth2Service *service,
+ ESource *source)
+{
+ EOAuth2ServiceMicrosoft365 *oauth2_microsoft365 = E_OAUTH2_SERVICE_MICROSOFT365 (service);
+ CamelM365Settings *m365_settings;
+ const gchar *res;
+
+ m365_settings = eos_microsoft365_get_camel_settings (source);
+ if (m365_settings && camel_m365_settings_get_override_oauth2 (m365_settings)) {
+ gchar *redirect_uri;
+
+ redirect_uri = camel_m365_settings_dup_oauth2_redirect_uri (m365_settings);
+
+ if (redirect_uri && !*redirect_uri) {
+ g_free (redirect_uri);
+ redirect_uri = NULL;
+ }
+
+ if (redirect_uri)
+ return eos_microsoft365_cache_string (oauth2_microsoft365, redirect_uri);
+ }
+
+ res = MICROSOFT365_REDIRECT_URI;
+ if (res && *res)
+ return res;
+
+ return "https://login.microsoftonline.com/common/oauth2/nativeclient";
+}
+
+static void
+eos_microsoft365_prepare_authentication_uri_query (EOAuth2Service *service,
+ ESource *source,
+ GHashTable *uri_query)
+{
+ g_return_if_fail (uri_query != NULL);
+
+ e_oauth2_service_util_set_to_form (uri_query, "response_type", "code");
+ e_oauth2_service_util_set_to_form (uri_query, "scope", MICROSOFT365_SCOPE);
+ e_oauth2_service_util_set_to_form (uri_query, "response_mode", "query");
+}
+
+static gboolean
+eos_microsoft365_extract_authorization_code (EOAuth2Service *service,
+ ESource *source,
+ const gchar *page_title,
+ const gchar *page_uri,
+ const gchar *page_content,
+ gchar **out_authorization_code)
+{
+ SoupURI *suri;
+ gboolean known = FALSE;
+
+ g_return_val_if_fail (out_authorization_code != NULL, FALSE);
+
+ *out_authorization_code = NULL;
+
+ if (!page_uri || !*page_uri)
+ return FALSE;
+
+ suri = soup_uri_new (page_uri);
+ if (!suri)
+ return FALSE;
+
+ if (suri->query) {
+ GHashTable *uri_query = soup_form_decode (suri->query);
+
+ if (uri_query) {
+ const gchar *code;
+
+ code = g_hash_table_lookup (uri_query, "code");
+
+ if (code && *code) {
+ *out_authorization_code = g_strdup (code);
+ known = TRUE;
+ } else if (g_hash_table_lookup (uri_query, "error")) {
+ known = TRUE;
+ if (g_strcmp0 (g_hash_table_lookup (uri_query, "error"), "access_denied") !=
0) {
+ const gchar *description;
+
+ description = g_hash_table_lookup (uri_query, "error_description");
+ if (description) {
+ g_warning ("%s: error:%s description:%s", G_STRFUNC,
+ (const gchar *) g_hash_table_lookup (uri_query,
"error"),
+ description);
+ }
+ }
+ }
+
+ g_hash_table_unref (uri_query);
+ }
+ }
+
+ soup_uri_free (suri);
+
+ return known;
+}
+
+static void
+eos_microsoft365_prepare_refresh_token_form (EOAuth2Service *service,
+ ESource *source,
+ const gchar *refresh_token,
+ GHashTable *form)
+{
+ g_return_if_fail (form != NULL);
+
+ e_oauth2_service_util_set_to_form (form, "scope", MICROSOFT365_SCOPE);
+ e_oauth2_service_util_set_to_form (form, "redirect_uri", e_oauth2_service_get_redirect_uri (service,
source));
+}
+
+static void
+eos_microsoft365_finalize (GObject *object)
+{
+ EOAuth2ServiceMicrosoft365 *oauth2_microsoft365 = E_OAUTH2_SERVICE_MICROSOFT365 (object);
+
+ g_mutex_lock (&oauth2_microsoft365->priv->string_cache_lock);
+ g_hash_table_destroy (oauth2_microsoft365->priv->string_cache);
+ g_mutex_unlock (&oauth2_microsoft365->priv->string_cache_lock);
+ g_mutex_clear (&oauth2_microsoft365->priv->string_cache_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_oauth2_service_microsoft365_parent_class)->finalize (object);
+}
+
+static void
+e_oauth2_service_microsoft365_oauth2_service_init (EOAuth2ServiceInterface *iface)
+{
+ iface->guess_can_process = eos_microsoft365_guess_can_process;
+ iface->get_name = eos_microsoft365_get_name;
+ iface->get_display_name = eos_microsoft365_get_display_name;
+ iface->get_client_id = eos_microsoft365_get_client_id;
+ iface->get_client_secret = eos_microsoft365_get_client_secret;
+ iface->get_authentication_uri = eos_microsoft365_get_authentication_uri;
+ iface->get_refresh_uri = eos_microsoft365_get_refresh_uri;
+ iface->get_redirect_uri = eos_microsoft365_get_redirect_uri;
+ iface->prepare_authentication_uri_query = eos_microsoft365_prepare_authentication_uri_query;
+ iface->extract_authorization_code = eos_microsoft365_extract_authorization_code;
+ iface->prepare_refresh_token_form = eos_microsoft365_prepare_refresh_token_form;
+}
+
+static void
+e_oauth2_service_microsoft365_class_init (EOAuth2ServiceMicrosoft365Class *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = eos_microsoft365_finalize;
+}
+
+static void
+e_oauth2_service_microsoft365_class_finalize (EOAuth2ServiceMicrosoft365Class *klass)
+{
+}
+
+static void
+e_oauth2_service_microsoft365_init (EOAuth2ServiceMicrosoft365 *oauth2_microsoft365)
+{
+ oauth2_microsoft365->priv = e_oauth2_service_microsoft365_get_instance_private (oauth2_microsoft365);
+
+ g_mutex_init (&oauth2_microsoft365->priv->string_cache_lock);
+ oauth2_microsoft365->priv->string_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
NULL);
+}
+
+void
+e_oauth2_service_microsoft365_type_register (GTypeModule *type_module)
+{
+ e_oauth2_service_microsoft365_register_type (type_module);
+}
diff --git a/src/Microsoft365/common/e-oauth2-service-microsoft365.h
b/src/Microsoft365/common/e-oauth2-service-microsoft365.h
new file mode 100644
index 00000000..0e76d93f
--- /dev/null
+++ b/src/Microsoft365/common/e-oauth2-service-microsoft365.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_OAUTH2_SERVICE_MICROSOFT365_H
+#define E_OAUTH2_SERVICE_MICROSOFT365_H
+
+#include <gmodule.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_OAUTH2_SERVICE_MICROSOFT365 \
+ (e_oauth2_service_microsoft365_get_type ())
+#define E_OAUTH2_SERVICE_MICROSOFT365(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_OAUTH2_SERVICE_MICROSOFT365, EOAuth2ServiceMicrosoft365))
+#define E_OAUTH2_SERVICE_MICROSOFT365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_OAUTH2_SERVICE_MICROSOFT365, EOAuth2ServiceMicrosoft365Class))
+#define E_IS_OAUTH2_SERVICE_MICROSOFT365(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_OAUTH2_SERVICE_MICROSOFT365))
+#define E_IS_OAUTH2_SERVICE_MICROSOFT365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_OAUTH2_SERVICE_MICROSOFT365))
+#define E_OAUTH2_SERVICE_MICROSOFT365_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_OAUTH2_SERVICE_MICROSOFT365, EOAuth2ServiceMicrosoft365Class))
+
+G_BEGIN_DECLS
+
+typedef struct _EOAuth2ServiceMicrosoft365 EOAuth2ServiceMicrosoft365;
+typedef struct _EOAuth2ServiceMicrosoft365Class EOAuth2ServiceMicrosoft365Class;
+typedef struct _EOAuth2ServiceMicrosoft365Private EOAuth2ServiceMicrosoft365Private;
+
+struct _EOAuth2ServiceMicrosoft365 {
+ EOAuth2ServiceBase parent;
+ EOAuth2ServiceMicrosoft365Private *priv;
+};
+
+struct _EOAuth2ServiceMicrosoft365Class {
+ EOAuth2ServiceBaseClass parent_class;
+};
+
+GType e_oauth2_service_microsoft365_get_type (void) G_GNUC_CONST;
+
+void e_oauth2_service_microsoft365_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_OAUTH2_SERVICE_MICROSOFT365_H */
diff --git a/src/Microsoft365/common/e-source-m365-folder.c b/src/Microsoft365/common/e-source-m365-folder.c
new file mode 100644
index 00000000..f333d1a9
--- /dev/null
+++ b/src/Microsoft365/common/e-source-m365-folder.c
@@ -0,0 +1,287 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include "e-source-m365-folder.h"
+
+struct _ESourceM365FolderPrivate {
+ gchar *id;
+ gchar *group_id;
+ gboolean is_default;
+};
+
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_IS_DEFAULT,
+ PROP_GROUP_ID
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ESourceM365Folder, e_source_m365_folder, E_TYPE_SOURCE_EXTENSION)
+
+static void
+source_m365_folder_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IS_DEFAULT:
+ e_source_m365_folder_set_is_default (
+ E_SOURCE_M365_FOLDER (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_ID:
+ e_source_m365_folder_set_id (
+ E_SOURCE_M365_FOLDER (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_GROUP_ID:
+ e_source_m365_folder_set_group_id (
+ E_SOURCE_M365_FOLDER (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_m365_folder_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IS_DEFAULT:
+ g_value_set_boolean (
+ value,
+ e_source_m365_folder_get_is_default (
+ E_SOURCE_M365_FOLDER (object)));
+ return;
+
+ case PROP_ID:
+ g_value_take_string (
+ value,
+ e_source_m365_folder_dup_id (
+ E_SOURCE_M365_FOLDER (object)));
+ return;
+
+ case PROP_GROUP_ID:
+ g_value_take_string (
+ value,
+ e_source_m365_folder_dup_group_id (
+ E_SOURCE_M365_FOLDER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_m365_folder_finalize (GObject *object)
+{
+ ESourceM365Folder *m365_folder = E_SOURCE_M365_FOLDER (object);
+
+ g_free (m365_folder->priv->id);
+ g_free (m365_folder->priv->group_id);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_source_m365_folder_parent_class)->finalize (object);
+}
+
+static void
+e_source_m365_folder_class_init (ESourceM365FolderClass *class)
+{
+ GObjectClass *object_class;
+ ESourceExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = source_m365_folder_set_property;
+ object_class->get_property = source_m365_folder_get_property;
+ object_class->finalize = source_m365_folder_finalize;
+
+ extension_class = E_SOURCE_EXTENSION_CLASS (class);
+ extension_class->name = E_SOURCE_EXTENSION_M365_FOLDER;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ID,
+ g_param_spec_string (
+ "id",
+ "ID",
+ "The server-assigned folder ID",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS |
+ E_SOURCE_PARAM_SETTING));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IS_DEFAULT,
+ g_param_spec_boolean (
+ "is-default",
+ "Is Default",
+ "Whether it's user's default folder (like 'contacts', which are not part of the
contactFolders)",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS |
+ E_SOURCE_PARAM_SETTING));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_GROUP_ID,
+ g_param_spec_string (
+ "group-id",
+ "Group ID",
+ "Optional group ID, into which the folder ID belongs",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS |
+ E_SOURCE_PARAM_SETTING));
+}
+
+static void
+e_source_m365_folder_init (ESourceM365Folder *extension)
+{
+ extension->priv = e_source_m365_folder_get_instance_private (extension);
+}
+
+void
+e_source_m365_folder_type_register (GTypeModule *type_module)
+{
+ /* We need to ensure this is registered, because it's looked up
+ * by name in e_source_get_extension(). */
+ g_type_ensure (E_TYPE_SOURCE_M365_FOLDER);
+}
+
+const gchar *
+e_source_m365_folder_get_id (ESourceM365Folder *extension)
+{
+ g_return_val_if_fail (E_IS_SOURCE_M365_FOLDER (extension), NULL);
+
+ return extension->priv->id;
+}
+
+gchar *
+e_source_m365_folder_dup_id (ESourceM365Folder *extension)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (E_IS_SOURCE_M365_FOLDER (extension), NULL);
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ protected = e_source_m365_folder_get_id (extension);
+ duplicate = g_strdup (protected);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ return duplicate;
+}
+
+void
+e_source_m365_folder_set_id (ESourceM365Folder *extension,
+ const gchar *id)
+{
+ g_return_if_fail (E_IS_SOURCE_M365_FOLDER (extension));
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ if (g_strcmp0 (extension->priv->id, id) == 0) {
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+ return;
+ }
+
+ g_free (extension->priv->id);
+ extension->priv->id = e_util_strdup_strip (id);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ g_object_notify (G_OBJECT (extension), "id");
+}
+
+gboolean
+e_source_m365_folder_get_is_default (ESourceM365Folder *extension)
+{
+ g_return_val_if_fail (E_IS_SOURCE_M365_FOLDER (extension), FALSE);
+
+ return extension->priv->is_default;
+}
+
+void
+e_source_m365_folder_set_is_default (ESourceM365Folder *extension,
+ gboolean value)
+{
+ g_return_if_fail (E_IS_SOURCE_M365_FOLDER (extension));
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ if ((extension->priv->is_default ? 1 : 0) == (value ? 1 : 0)) {
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+ return;
+ }
+
+ extension->priv->is_default = value;
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ g_object_notify (G_OBJECT (extension), "is-default");
+}
+
+const gchar *
+e_source_m365_folder_get_group_id (ESourceM365Folder *extension)
+{
+ g_return_val_if_fail (E_IS_SOURCE_M365_FOLDER (extension), NULL);
+
+ return extension->priv->group_id;
+}
+
+gchar *
+e_source_m365_folder_dup_group_id (ESourceM365Folder *extension)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (E_IS_SOURCE_M365_FOLDER (extension), NULL);
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ protected = e_source_m365_folder_get_group_id (extension);
+ duplicate = g_strdup (protected);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ return duplicate;
+}
+
+void
+e_source_m365_folder_set_group_id (ESourceM365Folder *extension,
+ const gchar *group_id)
+{
+ g_return_if_fail (E_IS_SOURCE_M365_FOLDER (extension));
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ if (g_strcmp0 (extension->priv->group_id, group_id) == 0) {
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+ return;
+ }
+
+ g_free (extension->priv->group_id);
+ extension->priv->group_id = e_util_strdup_strip (group_id);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ g_object_notify (G_OBJECT (extension), "group-id");
+}
diff --git a/src/Microsoft365/common/e-source-m365-folder.h b/src/Microsoft365/common/e-source-m365-folder.h
new file mode 100644
index 00000000..0f8750e8
--- /dev/null
+++ b/src/Microsoft365/common/e-source-m365-folder.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_SOURCE_M365_FOLDER_H
+#define E_SOURCE_M365_FOLDER_H
+
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_M365_FOLDER \
+ (e_source_m365_folder_get_type ())
+#define E_SOURCE_M365_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SOURCE_M365_FOLDER, ESourceM365Folder))
+#define E_SOURCE_M365_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SOURCE_M365_FOLDER, ESourceM365FolderClass))
+#define E_IS_SOURCE_M365_FOLDER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SOURCE_M365_FOLDER))
+#define E_IS_SOURCE_M365_FOLDER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SOURCE_M365_FOLDER))
+#define E_SOURCE_M365_FOLDER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SOURCE_M365_FOLDER, ESourceM365FolderClass))
+
+#define E_SOURCE_EXTENSION_M365_FOLDER "Microsoft365 Folder"
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceM365Folder ESourceM365Folder;
+typedef struct _ESourceM365FolderClass ESourceM365FolderClass;
+typedef struct _ESourceM365FolderPrivate ESourceM365FolderPrivate;
+
+struct _ESourceM365Folder {
+ ESourceExtension parent;
+ ESourceM365FolderPrivate *priv;
+};
+
+struct _ESourceM365FolderClass {
+ ESourceExtensionClass parent_class;
+};
+
+GType e_source_m365_folder_get_type (void) G_GNUC_CONST;
+void e_source_m365_folder_type_register
+ (GTypeModule *type_module);
+const gchar * e_source_m365_folder_get_id (ESourceM365Folder *extension);
+gchar * e_source_m365_folder_dup_id (ESourceM365Folder *extension);
+void e_source_m365_folder_set_id (ESourceM365Folder *extension,
+ const gchar *id);
+gboolean e_source_m365_folder_get_is_default
+ (ESourceM365Folder *extension);
+void e_source_m365_folder_set_is_default
+ (ESourceM365Folder *extension,
+ gboolean value);
+const gchar * e_source_m365_folder_get_group_id
+ (ESourceM365Folder *extension);
+gchar * e_source_m365_folder_dup_group_id
+ (ESourceM365Folder *extension);
+void e_source_m365_folder_set_group_id
+ (ESourceM365Folder *extension,
+ const gchar *group_id);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_M365_FOLDER_H */
diff --git a/src/Microsoft365/evolution/CMakeLists.txt b/src/Microsoft365/evolution/CMakeLists.txt
new file mode 100644
index 00000000..e239083b
--- /dev/null
+++ b/src/Microsoft365/evolution/CMakeLists.txt
@@ -0,0 +1,39 @@
+set(sources
+ e-cal-config-m365.c
+ e-cal-config-m365.h
+ e-book-config-m365.c
+ e-book-config-m365.h
+ e-mail-config-m365-backend.c
+ e-mail-config-m365-backend.h
+ module-m365-configuration.c
+)
+set(extra_deps)
+set(extra_defines)
+set(extra_cflags
+ ${EVOLUTION_MAIL_CFLAGS}
+ ${EVOLUTION_SHELL_CFLAGS}
+ ${LIBECAL_CFLAGS}
+ ${LIBEBOOK_CFLAGS}
+)
+set(extra_incdirs
+ ${EVOLUTION_MAIL_INCLUDE_DIRS}
+ ${EVOLUTION_SHELL_INCLUDE_DIRS}
+ ${LIBECAL_INCLUDE_DIRS}
+ ${LIBEBOOK_INCLUDE_DIRS}
+)
+set(extra_ldflags
+ ${EVOLUTION_MAIL_LDFLAGS}
+ ${EVOLUTION_SHELL_LDFLAGS}
+ ${LIBECAL_LDFLAGS}
+ ${LIBEBOOK_LDFLAGS}
+)
+
+add_simple_module_m365(module-microsoft365-configuration
+ sources
+ extra_deps
+ extra_defines
+ extra_cflags
+ extra_incdirs
+ extra_ldflags
+ "${evo_moduledir}"
+)
diff --git a/src/Microsoft365/evolution/e-book-config-m365.c b/src/Microsoft365/evolution/e-book-config-m365.c
new file mode 100644
index 00000000..86afae36
--- /dev/null
+++ b/src/Microsoft365/evolution/e-book-config-m365.c
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-book-config-m365.h"
+
+G_DEFINE_DYNAMIC_TYPE (EBookConfigM365, e_book_config_m365, E_TYPE_SOURCE_CONFIG_BACKEND)
+
+static gboolean
+book_config_m365_allow_creation (ESourceConfigBackend *backend)
+{
+ return FALSE;
+}
+
+static void
+book_config_m365_insert_widgets (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ if (!scratch_source)
+ return;
+
+ e_source_config_add_refresh_interval (e_source_config_backend_get_config (backend), scratch_source);
+}
+
+static void
+e_book_config_m365_class_init (EBookConfigM365Class *class)
+{
+ EExtensionClass *extension_class;
+ ESourceConfigBackendClass *backend_class;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_BOOK_SOURCE_CONFIG;
+
+ backend_class = E_SOURCE_CONFIG_BACKEND_CLASS (class);
+ backend_class->backend_name = "microsoft365";
+ backend_class->allow_creation = book_config_m365_allow_creation;
+ backend_class->insert_widgets = book_config_m365_insert_widgets;
+}
+
+static void
+e_book_config_m365_class_finalize (EBookConfigM365Class *class)
+{
+}
+
+static void
+e_book_config_m365_init (EBookConfigM365 *backend)
+{
+}
+
+void
+e_book_config_m365_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_book_config_m365_register_type (type_module);
+}
diff --git a/src/Microsoft365/evolution/e-book-config-m365.h b/src/Microsoft365/evolution/e-book-config-m365.h
new file mode 100644
index 00000000..e619282d
--- /dev/null
+++ b/src/Microsoft365/evolution/e-book-config-m365.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_BOOK_CONFIG_M365_H
+#define E_BOOK_CONFIG_M365_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_CONFIG_M365 \
+ (e_book_config_m365_get_type ())
+#define E_BOOK_CONFIG_M365(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_BOOK_CONFIG_M365, EBookConfigM365))
+#define E_BOOK_CONFIG_M365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_BOOK_CONFIG_M365, EBookConfigM365Class))
+#define E_IS_BOOK_CONFIG_M365(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_BOOK_CONFIG_M365))
+#define E_IS_BOOK_CONFIG_M365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_BOOK_CONFIG_M365))
+#define E_BOOK_CONFIG_M365_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_BOOK_CONFIG_M365, EBookConfigM365Class))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookConfigM365 EBookConfigM365;
+typedef struct _EBookConfigM365Class EBookConfigM365Class;
+typedef struct _EBookConfigM365Private EBookConfigM365Private;
+
+struct _EBookConfigM365 {
+ ESourceConfigBackend parent;
+ EBookConfigM365Private *priv;
+};
+
+struct _EBookConfigM365Class {
+ ESourceConfigBackendClass parent_class;
+};
+
+GType e_book_config_m365_get_type (void) G_GNUC_CONST;
+void e_book_config_m365_type_register(GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_BOOK_CONFIG_M365_H */
diff --git a/src/Microsoft365/evolution/e-cal-config-m365.c b/src/Microsoft365/evolution/e-cal-config-m365.c
new file mode 100644
index 00000000..694bf3dc
--- /dev/null
+++ b/src/Microsoft365/evolution/e-cal-config-m365.c
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include "e-cal-config-m365.h"
+
+G_DEFINE_DYNAMIC_TYPE (ECalConfigM365, e_cal_config_m365, E_TYPE_SOURCE_CONFIG_BACKEND)
+
+static gboolean
+cal_config_m365_allow_creation (ESourceConfigBackend *backend)
+{
+#if 0
+ ESourceConfig *config;
+ ECalSourceConfig *cal_config;
+ ECalClientSourceType source_type;
+ gboolean allow_creation = FALSE;
+
+ config = e_source_config_backend_get_config (backend);
+
+ cal_config = E_CAL_SOURCE_CONFIG (config);
+ source_type = e_cal_source_config_get_source_type (cal_config);
+
+ switch (source_type) {
+ case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+ case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+ allow_creation = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ return allow_creation;
+#endif
+ return FALSE;
+}
+
+static void
+cal_config_m365_insert_widgets (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ if (!scratch_source)
+ return;
+
+ e_source_config_add_refresh_interval (e_source_config_backend_get_config (backend), scratch_source);
+}
+
+static void
+e_cal_config_m365_class_init (ECalConfigM365Class *class)
+{
+ EExtensionClass *extension_class;
+ ESourceConfigBackendClass *backend_class;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_CAL_SOURCE_CONFIG;
+
+ backend_class = E_SOURCE_CONFIG_BACKEND_CLASS (class);
+ backend_class->backend_name = "microsoft365";
+ backend_class->allow_creation = cal_config_m365_allow_creation;
+ backend_class->insert_widgets = cal_config_m365_insert_widgets;
+}
+
+static void
+e_cal_config_m365_class_finalize (ECalConfigM365Class *class)
+{
+}
+
+static void
+e_cal_config_m365_init (ECalConfigM365 *backend)
+{
+}
+
+void
+e_cal_config_m365_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_cal_config_m365_register_type (type_module);
+}
diff --git a/src/Microsoft365/evolution/e-cal-config-m365.h b/src/Microsoft365/evolution/e-cal-config-m365.h
new file mode 100644
index 00000000..9ac9b574
--- /dev/null
+++ b/src/Microsoft365/evolution/e-cal-config-m365.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_CAL_CONFIG_M365_H
+#define E_CAL_CONFIG_M365_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_CONFIG_M365 \
+ (e_cal_config_m365_get_type ())
+#define E_CAL_CONFIG_M365(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CAL_CONFIG_M365, ECalConfigM365))
+#define E_CAL_CONFIG_M365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CAL_CONFIG_M365, ECalConfigM365Class))
+#define E_IS_CAL_CONFIG_M365(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CAL_CONFIG_M365))
+#define E_IS_CAL_CONFIG_M365_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CAL_CONFIG_M365))
+#define E_CAL_CONFIG_M365_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CAL_CONFIG_M365, ECalConfigM365Class))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalConfigM365 ECalConfigM365;
+typedef struct _ECalConfigM365Class ECalConfigM365Class;
+typedef struct _ECalConfigM365Private ECalConfigM365Private;
+
+struct _ECalConfigM365 {
+ ESourceConfigBackend parent;
+ ECalConfigM365Private *priv;
+};
+
+struct _ECalConfigM365Class {
+ ESourceConfigBackendClass parent_class;
+};
+
+GType e_cal_config_m365_get_type (void) G_GNUC_CONST;
+void e_cal_config_m365_type_register (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_CAL_CONFIG_M365_H */
diff --git a/src/Microsoft365/evolution/e-mail-config-m365-backend.c
b/src/Microsoft365/evolution/e-mail-config-m365-backend.c
new file mode 100644
index 00000000..8da11fb4
--- /dev/null
+++ b/src/Microsoft365/evolution/e-mail-config-m365-backend.c
@@ -0,0 +1,492 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+#include <libebackend/libebackend.h>
+
+#include <mail/e-mail-config-auth-check.h>
+#include <mail/e-mail-config-receiving-page.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+
+#include "e-mail-config-m365-backend.h"
+
+struct _EMailConfigM365BackendPrivate {
+ GtkWidget *user_entry;
+ GtkWidget *impersonate_user_entry;
+ GtkGrid *oauth2_settings_grid;
+ GtkWidget *oauth2_override_check;
+ GtkWidget *oauth2_tenant_entry;
+ GtkWidget *oauth2_client_id_entry;
+ GtkWidget *oauth2_redirect_uri_entry;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (EMailConfigM365Backend, e_mail_config_m365_backend,
E_TYPE_MAIL_CONFIG_SERVICE_BACKEND, 0,
+ G_ADD_PRIVATE_DYNAMIC (EMailConfigM365Backend))
+
+static ESource *
+mail_config_m365_backend_new_collection (EMailConfigServiceBackend *backend)
+{
+ EMailConfigServiceBackendClass *class;
+ ESourceBackend *extension;
+ ESource *source;
+ const gchar *extension_name;
+
+ /* This backend serves double duty. One instance holds the
+ * mail account source, another holds the mail transport source.
+ * We can differentiate by examining the EMailConfigServicePage
+ * the backend is associated with. We return a new collection
+ * for both the Receiving Page and Sending Page. Although the
+ * Sending Page instance ultimately gets discarded, it's still
+ * needed to avoid creating an [Microsoft365 Backend] extension
+ * in the mail transport source. */
+
+ class = E_MAIL_CONFIG_SERVICE_BACKEND_GET_CLASS (backend);
+
+ source = e_source_new (NULL, NULL, NULL);
+ extension_name = E_SOURCE_EXTENSION_COLLECTION;
+ extension = e_source_get_extension (source, extension_name);
+ e_source_backend_set_backend_name (extension, class->backend_name);
+
+ return source;
+}
+
+static void
+mail_config_m365_backend_set_oauth2_tooltip (GtkWidget *widget,
+ const gchar *value,
+ const gchar *when_value_empty,
+ gchar *when_value_filled) /* takes ownership */
+{
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ gtk_widget_set_tooltip_text (widget, value && *value ? when_value_filled : when_value_empty);
+
+ g_free (when_value_filled);
+}
+
+static void
+mail_config_m365_backend_insert_widgets (EMailConfigServiceBackend *backend,
+ GtkBox *parent)
+{
+ EMailConfigM365Backend *m365_backend;
+ EMailConfigServicePage *page;
+ ESource *source;
+ ESourceExtension *extension;
+ ESourceAuthentication *auth_extension;
+ CamelSettings *settings;
+ GtkLabel *label;
+ GtkWidget *widget;
+ GtkWidget *container;
+ const gchar *extension_name;
+ const gchar *text;
+ gchar *markup;
+
+ m365_backend = E_MAIL_CONFIG_M365_BACKEND (backend);
+ page = e_mail_config_service_backend_get_page (backend);
+
+ /* This backend serves double duty. One instance holds the
+ * mail account source, another holds the mail transport source.
+ * We can differentiate by examining the EMailConfigServicePage
+ * the backend is associated with. This method only applies to
+ * the Receiving Page. */
+ if (!E_IS_MAIL_CONFIG_RECEIVING_PAGE (page))
+ return;
+
+ /* This needs to come _after_ the page type check so we don't
+ * introduce a backend extension in the mail transport source. */
+ settings = e_mail_config_service_backend_get_settings (backend);
+
+ text = _("Configuration");
+ markup = g_markup_printf_escaped ("<b>%s</b>", text);
+ widget = gtk_label_new (markup);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (parent), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_grid_new ();
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_grid_set_row_spacing (GTK_GRID (widget), 6);
+ gtk_grid_set_column_spacing (GTK_GRID (widget), 6);
+ gtk_box_pack_start (GTK_BOX (parent), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("User_name:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 1);
+ gtk_widget_show (widget);
+
+ label = GTK_LABEL (widget);
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_label_set_mnemonic_widget (label, widget);
+ gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 2, 1);
+ m365_backend->priv->user_entry = widget; /* do not reference */
+ gtk_widget_show (widget);
+
+ widget = gtk_check_button_new_with_mnemonic (_("Open _Mailbox of other user"));
+ gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
+ gtk_widget_show (widget);
+
+ if (camel_m365_settings_get_use_impersonation (CAMEL_M365_SETTINGS (settings))) {
+ const gchar *impersonate_user = camel_m365_settings_get_impersonate_user (CAMEL_M365_SETTINGS
(settings));
+
+ if (impersonate_user && !*impersonate_user) {
+ camel_m365_settings_set_impersonate_user (CAMEL_M365_SETTINGS (settings), NULL);
+ camel_m365_settings_set_use_impersonation (CAMEL_M365_SETTINGS (settings), FALSE);
+ }
+ }
+
+ e_binding_bind_property (
+ settings, "use-impersonation",
+ widget, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
+ gtk_widget_show (widget);
+ m365_backend->priv->impersonate_user_entry = widget; /* do not reference */
+
+ e_binding_bind_object_text_property (
+ settings, "impersonate-user",
+ widget, "text",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ settings, "use-impersonation",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ text = _("Authentication");
+ markup = g_markup_printf_escaped ("<b>%s</b>", text);
+ widget = gtk_label_new (markup);
+ gtk_widget_set_margin_top (widget, 6);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (parent), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ widget = gtk_grid_new ();
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_box_pack_start (GTK_BOX (parent), widget, FALSE, FALSE, 0);
+ m365_backend->priv->oauth2_settings_grid = GTK_GRID (widget);
+
+ gtk_grid_set_column_spacing (m365_backend->priv->oauth2_settings_grid, 4);
+ gtk_grid_set_row_spacing (m365_backend->priv->oauth2_settings_grid, 4);
+
+ container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, container, 0, 0, 2, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Override Microsoft 365 OAuth2 settings"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ m365_backend->priv->oauth2_override_check = widget;
+
+ markup = g_markup_printf_escaped ("(<a
href=\"https://wiki.gnome.org/Apps/Evolution/EWS/OAuth2\">%s</a>)", _("Helpā¦"));
+ widget = gtk_label_new (markup);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ g_free (markup);
+
+ /* Translators: 'Tenant' here means a term used by Microsoft to identify a company or organization in
a Microsoft 365 world.
+ You probably do not want to translate it. More for example here:
https://powerbi.microsoft.com/en-us/blog/what-is-a-tenant/ */
+ widget = gtk_label_new_with_mnemonic (_("_Tenant:"));
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 0, 1, 1, 1);
+ label = GTK_LABEL (widget);
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_label_set_mnemonic_widget (label, widget);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 1, 1, 1, 1);
+ m365_backend->priv->oauth2_tenant_entry = widget;
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ mail_config_m365_backend_set_oauth2_tooltip (widget, MICROSOFT365_TENANT,
+ /* Translators: 'Tenant' here means a term used by Microsoft to identify a company or
organization in a Microsoft 365 world. Same for 'common', it's a default URL path.
+ You probably do not want to translate it. More for example here:
https://powerbi.microsoft.com/en-us/blog/what-is-a-tenant/ */
+ _("Default tenant is ācommonā"),
+ /* Translators: 'Tenant' here means a term used by Microsoft to identify a company or
organization in a Microsoft 365 world.
+ You probably do not want to translate it. More for example here:
https://powerbi.microsoft.com/en-us/blog/what-is-a-tenant/ */
+ g_strdup_printf (_("Default tenant is ā%sā"), MICROSOFT365_TENANT));
+
+ widget = gtk_label_new_with_mnemonic (_("Application I_D:"));
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 0, 2, 1, 1);
+ label = GTK_LABEL (widget);
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_label_set_mnemonic_widget (label, widget);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 1, 2, 1, 1);
+ m365_backend->priv->oauth2_client_id_entry = widget;
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ mail_config_m365_backend_set_oauth2_tooltip (widget, MICROSOFT365_CLIENT_ID,
+ _("There is not set any default application ID"),
+ g_strdup_printf (_("Default application ID is ā%sā"), MICROSOFT365_CLIENT_ID));
+
+ widget = gtk_label_new_with_mnemonic (_("_Redirect URI:"));
+ gtk_widget_set_margin_left (widget, 12);
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 0, 3, 1, 1);
+ label = GTK_LABEL (widget);
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_label_set_mnemonic_widget (label, widget);
+ gtk_grid_attach (m365_backend->priv->oauth2_settings_grid, widget, 1, 3, 1, 1);
+ m365_backend->priv->oauth2_redirect_uri_entry = widget;
+
+ e_binding_bind_property (
+ m365_backend->priv->oauth2_override_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ markup = g_strdup_printf (_("Default redirect URI is ā%sā"),
"https://login.microsoftonline.com/common/oauth2/nativeclient");
+ mail_config_m365_backend_set_oauth2_tooltip (widget, MICROSOFT365_REDIRECT_URI,
+ markup,
+ g_strdup_printf (_("Default redirect URI is ā%sā"), MICROSOFT365_REDIRECT_URI));
+ g_free (markup);
+
+ gtk_widget_show_all (GTK_WIDGET (m365_backend->priv->oauth2_settings_grid));
+
+ e_binding_bind_object_text_property (
+ settings, "user",
+ m365_backend->priv->user_entry, "text",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ settings, "override-oauth2",
+ m365_backend->priv->oauth2_override_check, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_object_text_property (
+ settings, "oauth2-tenant",
+ m365_backend->priv->oauth2_tenant_entry, "text",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_object_text_property (
+ settings, "oauth2-client-id",
+ m365_backend->priv->oauth2_client_id_entry, "text",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_object_text_property (
+ settings, "oauth2-redirect_uri",
+ m365_backend->priv->oauth2_redirect_uri_entry, "text",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ extension_name = E_SOURCE_EXTENSION_COLLECTION;
+ source = e_mail_config_service_backend_get_collection (backend);
+ extension = e_source_get_extension (source, extension_name);
+
+ /* The collection identity is the user name. */
+ e_binding_bind_property (
+ settings, "user",
+ extension, "identity",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+ e_source_authentication_set_host (auth_extension, "graph.microsoft.com");
+ e_source_authentication_set_port (auth_extension, 442);
+ e_source_authentication_set_method (auth_extension, "Microsoft365");
+}
+
+static void
+mail_config_m365_backend_setup_defaults (EMailConfigServiceBackend *backend)
+{
+ CamelSettings *settings;
+ EMailConfigServicePage *page;
+ const gchar *email_address;
+
+ page = e_mail_config_service_backend_get_page (backend);
+
+ /* This backend serves double duty. One instance holds the
+ * mail account source, another holds the mail transport source.
+ * We can differentiate by examining the EMailConfigServicePage
+ * the backend is associated with. This method only applies to
+ * the Receiving Page. */
+ if (!E_IS_MAIL_CONFIG_RECEIVING_PAGE (page))
+ return;
+
+ /* This needs to come _after_ the page type check so we don't
+ * introduce a backend extension in the mail transport source. */
+ email_address = e_mail_config_service_page_get_email_address (page);
+ settings = e_mail_config_service_backend_get_settings (backend);
+
+ camel_m365_settings_set_email (CAMEL_M365_SETTINGS (settings), email_address);
+ camel_network_settings_set_user (CAMEL_NETWORK_SETTINGS (settings), email_address);
+}
+
+static gboolean
+mail_config_m365_backend_auto_configure (EMailConfigServiceBackend *backend,
+ EConfigLookup *config_lookup,
+ gint *out_priority,
+ gboolean *out_is_complete)
+{
+ return e_mail_config_service_backend_auto_configure_for_kind (backend, config_lookup,
+ E_CONFIG_LOOKUP_RESULT_COLLECTION, NULL,
+ e_mail_config_service_backend_get_collection (backend),
+ out_priority, out_is_complete);
+}
+
+static gboolean
+mail_config_m365_backend_check_complete (EMailConfigServiceBackend *backend)
+{
+ EMailConfigServicePage *page;
+ EMailConfigM365Backend *m365_backend;
+ CamelSettings *settings;
+ CamelNetworkSettings *network_settings;
+ const gchar *user;
+ gboolean correct, complete = TRUE;
+
+ m365_backend = E_MAIL_CONFIG_M365_BACKEND (backend);
+ page = e_mail_config_service_backend_get_page (backend);
+
+ /* This backend serves double duty. One instance holds the
+ * mail account source, another holds the mail transport source.
+ * We can differentiate by examining the EMailConfigServicePage
+ * the backend is associated with. This method only applies to
+ * the Receiving Page. */
+ if (!E_IS_MAIL_CONFIG_RECEIVING_PAGE (page))
+ return TRUE;
+
+ /* This needs to come _after_ the page type check so we don't
+ * introduce a backend extension in the mail transport source. */
+ settings = e_mail_config_service_backend_get_settings (backend);
+
+ network_settings = CAMEL_NETWORK_SETTINGS (settings);
+ user = camel_network_settings_get_user (network_settings);
+
+ correct = user != NULL && *user != '\0';
+ complete = complete && correct;
+
+ e_util_set_entry_issue_hint (m365_backend->priv->user_entry, correct ? NULL : _("User name cannot be
empty"));
+
+ if (correct) {
+ CamelM365Settings *m365_settings = CAMEL_M365_SETTINGS (settings);
+ const gchar *client_id;
+
+ if (camel_m365_settings_get_override_oauth2 (m365_settings)) {
+ client_id = camel_m365_settings_get_oauth2_client_id (m365_settings);
+ } else {
+ client_id = MICROSOFT365_CLIENT_ID;
+ }
+
+ correct = client_id && *client_id;
+ complete = complete && correct;
+
+ e_util_set_entry_issue_hint (m365_backend->priv->oauth2_client_id_entry, correct ? NULL :
_("Application ID cannot be empty"));
+ }
+
+ return complete;
+}
+
+static void
+mail_config_m365_backend_commit_changes (EMailConfigServiceBackend *backend)
+{
+ CamelSettings *settings;
+ CamelM365Settings *m365_settings;
+ EMailConfigServicePage *page;
+ const gchar *email_address;
+
+ page = e_mail_config_service_backend_get_page (backend);
+
+ /* This backend serves double duty. One instance holds the
+ * mail account source, another holds the mail transport source.
+ * We can differentiate by examining the EMailConfigServicePage
+ * the backend is associated with. This method only applies to
+ * the Receiving Page. */
+ if (!E_IS_MAIL_CONFIG_RECEIVING_PAGE (page))
+ return;
+
+ /* This needs to come _after_ the page type check so we don't
+ * introduce a backend extension in the mail transport source. */
+ settings = e_mail_config_service_backend_get_settings (backend);
+
+ email_address = e_mail_config_service_page_get_email_address (page);
+ if (email_address != NULL) {
+ m365_settings = CAMEL_M365_SETTINGS (settings);
+ camel_m365_settings_set_email (m365_settings, email_address);
+ }
+}
+
+static void
+e_mail_config_m365_backend_class_init (EMailConfigM365BackendClass *class)
+{
+ EMailConfigServiceBackendClass *backend_class;
+
+ backend_class = E_MAIL_CONFIG_SERVICE_BACKEND_CLASS (class);
+ backend_class->backend_name = "microsoft365";
+ backend_class->new_collection = mail_config_m365_backend_new_collection;
+ backend_class->insert_widgets = mail_config_m365_backend_insert_widgets;
+ backend_class->setup_defaults = mail_config_m365_backend_setup_defaults;
+ backend_class->auto_configure = mail_config_m365_backend_auto_configure;
+ backend_class->check_complete = mail_config_m365_backend_check_complete;
+ backend_class->commit_changes = mail_config_m365_backend_commit_changes;
+}
+
+static void
+e_mail_config_m365_backend_class_finalize (EMailConfigM365BackendClass *class)
+{
+}
+
+static void
+e_mail_config_m365_backend_init (EMailConfigM365Backend *backend)
+{
+ backend->priv = e_mail_config_m365_backend_get_instance_private (backend);
+}
+
+void
+e_mail_config_m365_backend_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_mail_config_m365_backend_register_type (type_module);
+}
diff --git a/src/Microsoft365/evolution/e-mail-config-m365-backend.h
b/src/Microsoft365/evolution/e-mail-config-m365-backend.h
new file mode 100644
index 00000000..1727d0de
--- /dev/null
+++ b/src/Microsoft365/evolution/e-mail-config-m365-backend.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_MAIL_CONFIG_M365_BACKEND_H
+#define E_MAIL_CONFIG_M365_BACKEND_H
+
+#include <mail/e-mail-config-service-backend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_CONFIG_M365_BACKEND \
+ (e_mail_config_m365_backend_get_type ())
+#define E_MAIL_CONFIG_M365_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_MAIL_CONFIG_M365_BACKEND, EMailConfigM365Backend))
+#define E_MAIL_CONFIG_M365_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_MAIL_CONFIG_M365_BACKEND, EMailConfigM365BackendClass))
+#define E_IS_MAIL_CONFIG_M365_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_MAIL_CONFIG_M365_BACKEND))
+#define E_IS_MAIL_CONFIG_M365_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_MAIL_CONFIG_M365_BACKEND))
+#define E_MAIL_CONFIG_M365_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_MAIL_CONFIG_M365_BACKEND, EMailConfigM365BackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailConfigM365Backend EMailConfigM365Backend;
+typedef struct _EMailConfigM365BackendClass EMailConfigM365BackendClass;
+typedef struct _EMailConfigM365BackendPrivate EMailConfigM365BackendPrivate;
+
+struct _EMailConfigM365Backend {
+ EMailConfigServiceBackend parent;
+ EMailConfigM365BackendPrivate *priv;
+};
+
+struct _EMailConfigM365BackendClass {
+ EMailConfigServiceBackendClass parent_class;
+};
+
+GType e_mail_config_m365_backend_get_type
+ (void) G_GNUC_CONST;
+void e_mail_config_m365_backend_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_MAIL_CONFIG_M365_BACKEND_H */
diff --git a/src/Microsoft365/evolution/module-m365-configuration.c
b/src/Microsoft365/evolution/module-m365-configuration.c
new file mode 100644
index 00000000..f6629f1c
--- /dev/null
+++ b/src/Microsoft365/evolution/module-m365-configuration.c
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "common/camel-sasl-xoauth2-microsoft365.h"
+#include "common/e-oauth2-service-microsoft365.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-cal-config-m365.h"
+#include "e-book-config-m365.h"
+#include "e-mail-config-m365-backend.h"
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, M365_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ camel_sasl_xoauth2_microsoft365_type_register (type_module);
+ e_oauth2_service_microsoft365_type_register (type_module);
+ e_source_m365_folder_type_register (type_module);
+
+ e_cal_config_m365_type_register (type_module);
+ e_book_config_m365_type_register (type_module);
+ e_mail_config_m365_backend_type_register (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/src/Microsoft365/registry/CMakeLists.txt b/src/Microsoft365/registry/CMakeLists.txt
new file mode 100644
index 00000000..d7888446
--- /dev/null
+++ b/src/Microsoft365/registry/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(extra_deps)
+set(sources
+ module-m365-backend.c
+ e-m365-backend.c
+ e-m365-backend.h
+ e-m365-backend-factory.c
+ e-m365-backend-factory.h
+ e-source-m365-deltas.c
+ e-source-m365-deltas.h
+)
+set(extra_defines)
+set(extra_cflags)
+set(extra_incdirs)
+set(extra_ldflags)
+
+add_simple_module_m365(module-microsoft365-backend
+ sources
+ extra_deps
+ extra_defines
+ extra_cflags
+ extra_incdirs
+ extra_ldflags
+ "${eds_moduledir}"
+)
diff --git a/src/Microsoft365/registry/e-m365-backend-factory.c
b/src/Microsoft365/registry/e-m365-backend-factory.c
new file mode 100644
index 00000000..303e0794
--- /dev/null
+++ b/src/Microsoft365/registry/e-m365-backend-factory.c
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include "e-m365-backend.h"
+
+#include "e-m365-backend-factory.h"
+
+G_DEFINE_DYNAMIC_TYPE (EM365BackendFactory, e_m365_backend_factory, E_TYPE_COLLECTION_BACKEND_FACTORY)
+
+static void
+m365_backend_prepare_mail_account_source (ESource *source)
+{
+ ESourceBackend *extension;
+ const gchar *extension_name;
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
+ extension = e_source_get_extension (source, extension_name);
+ e_source_backend_set_backend_name (extension, "microsoft365");
+}
+
+static void
+m365_backend_prepare_mail_transport_source (ESource *source)
+{
+ ESourceBackend *extension;
+ const gchar *extension_name;
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
+ extension = e_source_get_extension (source, extension_name);
+ e_source_backend_set_backend_name (extension, "microsoft365");
+}
+
+static void
+m365_backend_factory_prepare_mail (ECollectionBackendFactory *factory,
+ ESource *mail_account_source,
+ ESource *mail_identity_source,
+ ESource *mail_transport_source)
+{
+ /* Chain up to parent's method. */
+ E_COLLECTION_BACKEND_FACTORY_CLASS (e_m365_backend_factory_parent_class)->prepare_mail (factory,
mail_account_source, mail_identity_source, mail_transport_source);
+
+ m365_backend_prepare_mail_account_source (mail_account_source);
+ m365_backend_prepare_mail_transport_source (mail_transport_source);
+}
+
+static void
+e_m365_backend_factory_class_init (EM365BackendFactoryClass *class)
+{
+ ECollectionBackendFactoryClass *factory_class;
+
+ factory_class = E_COLLECTION_BACKEND_FACTORY_CLASS (class);
+ factory_class->factory_name = "microsoft365";
+ factory_class->backend_type = E_TYPE_M365_BACKEND;
+ factory_class->prepare_mail = m365_backend_factory_prepare_mail;
+}
+
+static void
+e_m365_backend_factory_class_finalize (EM365BackendFactoryClass *class)
+{
+}
+
+static void
+e_m365_backend_factory_init (EM365BackendFactory *factory)
+{
+}
+
+void
+e_m365_backend_factory_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_m365_backend_factory_register_type (type_module);
+}
diff --git a/src/Microsoft365/registry/e-m365-backend-factory.h
b/src/Microsoft365/registry/e-m365-backend-factory.h
new file mode 100644
index 00000000..4e497cce
--- /dev/null
+++ b/src/Microsoft365/registry/e-m365-backend-factory.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_BACKEND_FACTORY_H
+#define E_M365_BACKEND_FACTORY_H
+
+#include <libebackend/libebackend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_M365_BACKEND_FACTORY \
+ (e_m365_backend_factory_get_type ())
+#define E_M365_BACKEND_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_M365_BACKEND_FACTORY, EM365BackendFactory))
+#define E_M365_BACKEND_FACTORY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_M365_BACKEND_FACTORY, EM365BackendFactoryClass))
+#define E_IS_M365_BACKEND_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_M365_BACKEND_FACTORY))
+#define E_IS_M365_BACKEND_FACTORY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_M365_BACKEND_FACTORY))
+#define E_M365_BACKEND_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_M365_BACKEND_FACTORY, EM365BackendFactoryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EM365BackendFactory EM365BackendFactory;
+typedef struct _EM365BackendFactoryClass EM365BackendFactoryClass;
+typedef struct _EM365BackendFactoryPrivate EM365BackendFactoryPrivate;
+
+struct _EM365BackendFactory {
+ ECollectionBackendFactory parent;
+ EM365BackendFactoryPrivate *priv;
+};
+
+struct _EM365BackendFactoryClass {
+ ECollectionBackendFactoryClass parent_class;
+};
+
+GType e_m365_backend_factory_get_type (void) G_GNUC_CONST;
+void e_m365_backend_factory_type_register
+ (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_M365_BACKEND_FACTORY_H */
diff --git a/src/Microsoft365/registry/e-m365-backend.c b/src/Microsoft365/registry/e-m365-backend.c
new file mode 100644
index 00000000..e60f68fa
--- /dev/null
+++ b/src/Microsoft365/registry/e-m365-backend.c
@@ -0,0 +1,955 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "common/e-m365-connection.h"
+#include "common/e-source-m365-folder.h"
+#include "common/camel-m365-settings.h"
+
+#include "e-source-m365-deltas.h"
+
+#include "e-m365-backend.h"
+
+#define LOCK(_backend) g_mutex_lock (&_backend->priv->property_lock)
+#define UNLOCK(_backend) g_mutex_unlock (&_backend->priv->property_lock)
+
+struct _EM365BackendPrivate {
+ GMutex property_lock;
+
+ GHashTable *folder_sources; /* gchar *folder_id ~> ESource * */
+
+ gboolean need_update_folders;
+
+ gulong source_changed_id;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (EM365Backend, e_m365_backend, E_TYPE_COLLECTION_BACKEND, 0,
+ G_ADD_PRIVATE_DYNAMIC (EM365Backend))
+
+static void
+m365_backend_claim_old_resources (ECollectionBackend *backend)
+{
+ ESourceRegistryServer *registry;
+ GList *old_resources, *iter;
+
+ g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
+
+ registry = e_collection_backend_ref_server (backend);
+ old_resources = e_collection_backend_claim_all_resources (backend);
+
+ for (iter = old_resources; iter; iter = g_list_next (iter)) {
+ ESource *source = iter->data;
+
+ e_source_registry_server_add_source (registry, source);
+ }
+
+ g_list_free_full (old_resources, g_object_unref);
+ g_clear_object (®istry);
+}
+
+static void m365_backend_populate (ECollectionBackend *backend);
+
+static void
+m365_backend_source_changed_cb (ESource *source,
+ EM365Backend *backend)
+{
+ if (!e_source_get_enabled (source)) {
+ backend->priv->need_update_folders = TRUE;
+ return;
+ }
+
+ if (!backend->priv->need_update_folders)
+ return;
+
+ m365_backend_populate (E_COLLECTION_BACKEND (backend));
+}
+
+static void
+m365_backend_populate (ECollectionBackend *backend)
+{
+ ESource *source;
+ EM365Backend *m365_backend = E_M365_BACKEND (backend);
+
+ source = e_backend_get_source (E_BACKEND (backend));
+
+ m365_backend->priv->need_update_folders = TRUE;
+
+ if (!m365_backend->priv->source_changed_id) {
+ m365_backend->priv->source_changed_id = g_signal_connect (source, "changed",
+ G_CALLBACK (m365_backend_source_changed_cb), m365_backend);
+ }
+
+ /* do not do anything, if account is disabled */
+ if (!e_source_get_enabled (source))
+ return;
+
+ m365_backend_claim_old_resources (backend);
+
+ if (e_backend_get_online (E_BACKEND (backend)))
+ e_backend_schedule_authenticate (E_BACKEND (backend), NULL);
+}
+
+static void
+m365_backend_update_resource (EM365Backend *m365_backend,
+ const gchar *extension_name,
+ const gchar *id,
+ const gchar *group_id,
+ const gchar *display_name,
+ gboolean is_default,
+ const gchar *calendar_color)
+{
+ ESource *source;
+ gboolean is_new;
+
+ LOCK (m365_backend);
+ source = g_hash_table_lookup (m365_backend->priv->folder_sources, id);
+ if (source)
+ g_object_ref (source);
+ UNLOCK (m365_backend);
+
+ is_new = !source;
+
+ if (is_new)
+ source = e_collection_backend_new_child (E_COLLECTION_BACKEND (m365_backend), id);
+
+ if (source) {
+ e_source_set_display_name (source, display_name);
+
+ if (calendar_color && g_ascii_strcasecmp (calendar_color, "auto") != 0 && (
+ g_strcmp0 (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0 ||
+ g_strcmp0 (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0 ||
+ g_strcmp0 (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0)) {
+ ESourceSelectable *selectable;
+
+ selectable = e_source_get_extension (source, extension_name);
+ e_source_selectable_set_color (selectable, calendar_color);
+ }
+
+ if (is_new) {
+ ESourceRegistryServer *server;
+ gpointer extension;
+
+ extension = e_source_get_extension (source, extension_name);
+ e_source_backend_set_backend_name (E_SOURCE_BACKEND (extension), "microsoft365");
+
+ /* Do not notify with too old reminders */
+ if (g_strcmp0 (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0 ||
+ g_strcmp0 (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) {
+ ESourceAlarms *alarms;
+ gchar *today;
+ GTimeVal today_tv;
+ GDate dt;
+
+ g_date_clear (&dt, 1);
+ g_get_current_time (&today_tv);
+ g_date_set_time_val (&dt, &today_tv);
+
+ /* midnight UTC */
+ today = g_strdup_printf ("%04d-%02d-%02dT00:00:00Z", g_date_get_year (&dt),
g_date_get_month (&dt), g_date_get_day (&dt));
+
+ alarms = e_source_get_extension (source, E_SOURCE_EXTENSION_ALARMS);
+ e_source_alarms_set_last_notified (alarms, today);
+
+ g_free (today);
+ }
+
+ extension = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
+ e_source_m365_folder_set_id (extension, id);
+ e_source_m365_folder_set_group_id (extension, group_id);
+ e_source_m365_folder_set_is_default (extension, is_default);
+
+ server = e_collection_backend_ref_server (E_COLLECTION_BACKEND (m365_backend));
+
+ e_source_registry_server_add_source (server, source);
+
+ g_clear_object (&server);
+ }
+ }
+
+ g_clear_object (&source);
+}
+
+static void
+m365_backend_remove_resource (EM365Backend *m365_backend,
+ const gchar *extension_name,
+ const gchar *id) /* NULL to remove the "is-default" resource for the
extension_name */
+{
+ ESource *existing_source = NULL;
+
+ LOCK (m365_backend);
+
+ if (id) {
+ existing_source = g_hash_table_lookup (m365_backend->priv->folder_sources, id);
+ } else {
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, m365_backend->priv->folder_sources);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ ESource *source = value;
+
+ if (value && e_source_has_extension (source, extension_name) &&
+ e_source_m365_folder_get_is_default (e_source_get_extension (source,
E_SOURCE_EXTENSION_M365_FOLDER))) {
+ existing_source = source;
+ break;
+ }
+ }
+ }
+
+ if (existing_source)
+ g_object_ref (existing_source);
+
+ UNLOCK (m365_backend);
+
+ if (existing_source)
+ e_source_remove_sync (existing_source, NULL, NULL);
+
+ g_clear_object (&existing_source);
+}
+
+static GHashTable * /* gchar *uid ~> NULL */
+m365_backend_get_known_folder_ids (EM365Backend *m365_backend,
+ const gchar *extension_name,
+ gboolean with_the_default)
+{
+ GHashTable *ids;
+ GHashTableIter iter;
+ gpointer value;
+
+ ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ LOCK (m365_backend);
+
+ g_hash_table_iter_init (&iter, m365_backend->priv->folder_sources);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ ESource *source = value;
+
+ if (source && e_source_has_extension (source, extension_name)) {
+ ESourceM365Folder *m365_folder;
+
+ m365_folder = e_source_get_extension (source, E_SOURCE_EXTENSION_M365_FOLDER);
+
+ if (with_the_default || !e_source_m365_folder_get_is_default (m365_folder))
+ g_hash_table_insert (ids, e_source_m365_folder_dup_id (m365_folder), NULL);
+ }
+ }
+
+ UNLOCK (m365_backend);
+
+ return ids;
+}
+
+static void
+m365_backend_forget_folders_hash (EM365Backend *m365_backend,
+ const gchar *extension_name,
+ GHashTable *ids) /* gchar *id ~> NULL */
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, ids);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ const gchar *id = key;
+
+ if (id)
+ m365_backend_remove_resource (m365_backend, extension_name, id);
+ }
+}
+
+static void
+m365_backend_forget_folders (EM365Backend *m365_backend,
+ const gchar *extension_name,
+ gboolean with_the_default)
+{
+ GHashTable *ids;
+
+ ids = m365_backend_get_known_folder_ids (m365_backend, extension_name, with_the_default);
+
+ m365_backend_forget_folders_hash (m365_backend, extension_name, ids);
+
+ g_hash_table_destroy (ids);
+}
+
+static gboolean
+m365_backend_got_contact_folders_delta_cb (EM365Connection *cnc,
+ const GSList *results, /* JsonObject * - the returned objects from
the server */
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EM365Backend *m365_backend = user_data;
+ GSList *link;
+
+ g_return_val_if_fail (E_IS_M365_BACKEND (m365_backend), FALSE);
+
+ for (link = (GSList *) results; link; link = g_slist_next (link)) {
+ JsonObject *object = link->data;
+ const gchar *id = e_m365_folder_get_id (object);
+
+ if (!id)
+ continue;
+
+ if (e_m365_delta_is_removed_object (object)) {
+ m365_backend_remove_resource (m365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK, id);
+ } else {
+ m365_backend_update_resource (m365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ id, NULL, e_m365_folder_get_display_name (object),
+ FALSE, NULL);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+m365_backend_sync_contact_folders_sync (EM365Backend *m365_backend,
+ EM365Connection *cnc,
+ GCancellable *cancellable)
+{
+ EM365Folder *user_contacts = NULL;
+ ESourceM365Deltas *m365_deltas;
+ gchar *old_delta_link, *new_delta_link;
+ gboolean success;
+ GError *error = NULL;
+
+ m365_deltas = e_source_get_extension (e_backend_get_source (E_BACKEND (m365_backend)),
E_SOURCE_EXTENSION_M365_DELTAS);
+
+ if (e_m365_connection_get_contacts_folder_sync (cnc, NULL, NULL, NULL, &user_contacts, cancellable,
&error)) {
+ const gchar *id, *display_name;
+
+ id = e_m365_folder_get_id (user_contacts);
+ display_name = e_m365_folder_get_display_name (user_contacts);
+
+ g_warn_if_fail (id != NULL);
+ g_warn_if_fail (display_name != NULL);
+
+ m365_backend_update_resource (m365_backend,
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ id, NULL, display_name, TRUE, NULL);
+
+ json_object_unref (user_contacts);
+ } else if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_NOT_FOUND) ||
+ g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED)) {
+ m365_backend_remove_resource (m365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK, NULL);
+ }
+
+ g_clear_error (&error);
+
+ new_delta_link = NULL;
+ old_delta_link = e_source_m365_deltas_dup_contacts_link (m365_deltas);
+
+ success = e_m365_connection_get_folders_delta_sync (cnc, NULL, E_M365_FOLDER_KIND_CONTACTS, NULL,
old_delta_link, 0,
+ m365_backend_got_contact_folders_delta_cb, m365_backend, &new_delta_link, cancellable,
&error);
+
+ if (old_delta_link && *old_delta_link && e_m365_connection_util_delta_token_failed (error)) {
+ g_clear_pointer (&old_delta_link, g_free);
+ g_clear_error (&error);
+
+ m365_backend_forget_folders (m365_backend, E_SOURCE_EXTENSION_ADDRESS_BOOK, FALSE);
+
+ success = e_m365_connection_get_folders_delta_sync (cnc, NULL, E_M365_FOLDER_KIND_CONTACTS,
NULL, NULL, 0,
+ m365_backend_got_contact_folders_delta_cb, m365_backend, &new_delta_link,
cancellable, &error);
+ }
+
+ if (success)
+ e_source_m365_deltas_set_contacts_link (m365_deltas, new_delta_link);
+
+ g_clear_pointer (&old_delta_link, g_free);
+ g_clear_pointer (&new_delta_link, g_free);
+ g_clear_error (&error);
+}
+
+static void
+m365_backend_sync_calendar_folders_sync (EM365Backend *m365_backend,
+ EM365Connection *cnc,
+ GCancellable *cancellable)
+{
+ const gchar *extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ GHashTable *known_ids; /* gchar *id ~> NULL */
+ gboolean success = FALSE;
+ GSList *groups = NULL, *link;
+ GError *error = NULL;
+
+ known_ids = m365_backend_get_known_folder_ids (m365_backend, extension_name, FALSE);
+
+ if (e_m365_connection_list_calendar_groups_sync (cnc, NULL, &groups, cancellable, &error) && groups) {
+ success = TRUE;
+
+ for (link = groups; link && success; link = g_slist_next (link)) {
+ EM365CalendarGroup *group = link->data;
+ GSList *calendars = NULL;
+
+ if (!group)
+ continue;
+
+ if (e_m365_connection_list_calendars_sync (cnc, NULL, e_m365_calendar_group_get_id
(group), NULL, &calendars, cancellable, &error)) {
+ GSList *clink;
+
+ for (clink = calendars; clink; clink = g_slist_next (clink)) {
+ EM365Calendar *calendar = clink->data;
+
+ if (!calendar || !e_m365_calendar_get_id (calendar))
+ continue;
+
+ m365_backend_update_resource (m365_backend, extension_name,
+ e_m365_calendar_get_id (calendar),
+ e_m365_calendar_group_get_id (group),
+ e_m365_calendar_get_name (calendar),
+ FALSE,
+ e_m365_calendar_color_to_rgb (e_m365_calendar_get_color
(calendar)));
+
+ g_hash_table_remove (known_ids, e_m365_calendar_get_id (calendar));
+ }
+
+ g_slist_free_full (calendars, (GDestroyNotify) json_object_unref);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ g_slist_free_full (groups, (GDestroyNotify) json_object_unref);
+ }
+
+ if (success)
+ m365_backend_forget_folders_hash (m365_backend, extension_name, known_ids);
+
+ g_hash_table_destroy (known_ids);
+ g_clear_error (&error);
+}
+
+/* Tasks are in the beta stage (as of 2020-07-31) and even one can get groups of tasks, getting
+ information about a task folder in the group fails with an error NavigationNotSupported:
+ "Recursive navigation is not allowed after property 'TaskFolders' according to the entity schema."
+ The server returns similar error when trying to get a single task, which makes it unusable. */
+#if 0
+static void
+m365_backend_sync_task_folders_sync (EM365Backend *m365_backend,
+ EM365Connection *cnc,
+ GCancellable *cancellable)
+{
+ const gchar *extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ GHashTable *known_ids; /* gchar *id ~> NULL */
+ gboolean success = FALSE;
+ GSList *groups = NULL, *link;
+ GError *error = NULL;
+
+ known_ids = m365_backend_get_known_folder_ids (m365_backend, extension_name, FALSE);
+
+ if (e_m365_connection_list_task_groups_sync (cnc, NULL, &groups, cancellable, &error) && groups) {
+ success = TRUE;
+
+ for (link = groups; link && success; link = g_slist_next (link)) {
+ EM365TaskGroup *group = link->data;
+ GSList *task_folders = NULL;
+
+ if (!group)
+ continue;
+
+ if (e_m365_connection_list_task_folders_sync (cnc, NULL, e_m365_task_group_get_id
(group), NULL, &task_folders, cancellable, &error)) {
+ GSList *tlink;
+
+ for (tlink = task_folders; tlink; tlink = g_slist_next (tlink)) {
+ EM365TaskFolder *task_folder = tlink->data;
+
+ if (!task_folder || !e_m365_task_folder_get_id (task_folder))
+ continue;
+
+ m365_backend_update_resource (m365_backend, extension_name,
+ e_m365_task_folder_get_id (task_folder),
+ e_m365_task_group_get_id (group),
+ e_m365_task_folder_get_name (task_folder),
+ e_m365_task_folder_get_is_default_folder (task_folder),
+ NULL);
+
+ g_hash_table_remove (known_ids, e_m365_task_folder_get_id
(task_folder));
+ }
+
+ g_slist_free_full (task_folders, (GDestroyNotify) json_object_unref);
+ } else {
+ success = FALSE;
+ }
+ }
+
+ g_slist_free_full (groups, (GDestroyNotify) json_object_unref);
+ }
+
+ if (success)
+ m365_backend_forget_folders_hash (m365_backend, extension_name, known_ids);
+
+ g_hash_table_destroy (known_ids);
+ g_clear_error (&error);
+}
+#endif
+
+static void
+m365_backend_sync_folders_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ EM365Backend *m365_backend = source_object;
+ EM365Connection *cnc = task_data;
+ ESourceCollection *collection_extension = NULL;
+ ESource *source;
+
+ g_return_if_fail (E_IS_M365_BACKEND (m365_backend));
+ g_return_if_fail (E_IS_M365_CONNECTION (cnc));
+
+ source = e_backend_get_source (E_BACKEND (m365_backend));
+ collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+
+ if (e_source_collection_get_contacts_enabled (collection_extension)) {
+ m365_backend_sync_contact_folders_sync (m365_backend, cnc, cancellable);
+ }
+
+ if (e_source_collection_get_calendar_enabled (collection_extension)) {
+ m365_backend_sync_calendar_folders_sync (m365_backend, cnc, cancellable);
+#if 0
+ m365_backend_sync_task_folders_sync (m365_backend, cnc, cancellable);
+#endif
+ }
+}
+
+static void
+m365_backend_sync_folders (EM365Backend *m365_backend,
+ EM365Connection *cnc,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ m365_backend->priv->need_update_folders = FALSE;
+
+ task = g_task_new (m365_backend, cancellable, callback, user_data);
+
+ g_task_set_check_cancellable (task, TRUE);
+ g_task_set_task_data (task, g_object_ref (cnc), g_object_unref);
+ g_task_run_in_thread (task, m365_backend_sync_folders_thread);
+
+ g_object_unref (task);
+}
+
+static gchar *
+m365_backend_dup_resource_id (ECollectionBackend *backend,
+ ESource *child_source)
+{
+ ESourceM365Folder *extension;
+ const gchar *extension_name;
+
+ extension_name = E_SOURCE_EXTENSION_M365_FOLDER;
+ extension = e_source_get_extension (child_source, extension_name);
+
+ return e_source_m365_folder_dup_id (extension);
+}
+
+static void
+m365_backend_child_added (ECollectionBackend *backend,
+ ESource *child_source)
+{
+ ESource *collection_source;
+
+ collection_source = e_backend_get_source (E_BACKEND (backend));
+
+ if (e_source_has_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION) && (
+ e_source_has_extension (child_source, E_SOURCE_EXTENSION_MAIL_ACCOUNT) ||
+ e_source_has_extension (child_source, E_SOURCE_EXTENSION_MAIL_IDENTITY) ||
+ e_source_has_extension (child_source, E_SOURCE_EXTENSION_MAIL_TRANSPORT))) {
+ ESourceAuthentication *auth_child_extension;
+ ESourceCollection *collection_extension;
+
+ collection_extension = e_source_get_extension (collection_source,
E_SOURCE_EXTENSION_COLLECTION);
+ auth_child_extension = e_source_get_extension (child_source,
E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ e_binding_bind_property (
+ collection_extension, "identity",
+ auth_child_extension, "user",
+ G_BINDING_SYNC_CREATE);
+ }
+
+ /* We track M365 folders in a hash table by folder ID. */
+ if (e_source_has_extension (child_source, E_SOURCE_EXTENSION_M365_FOLDER)) {
+ ESourceM365Folder *extension;
+ gchar *folder_id;
+
+ extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_M365_FOLDER);
+ folder_id = e_source_m365_folder_dup_id (extension);
+
+ if (folder_id) {
+ EM365Backend *m365_backend = E_M365_BACKEND (backend);
+
+ LOCK (m365_backend);
+ g_hash_table_insert (m365_backend->priv->folder_sources, folder_id, g_object_ref
(child_source));
+ UNLOCK (m365_backend);
+ }
+ }
+
+ /* Chain up to parent's method. */
+ E_COLLECTION_BACKEND_CLASS (e_m365_backend_parent_class)->child_added (backend, child_source);
+}
+
+static void
+m365_backend_child_removed (ECollectionBackend *backend,
+ ESource *child_source)
+{
+ /* We track M365 folders in a hash table by folder ID. */
+ if (e_source_has_extension (child_source, E_SOURCE_EXTENSION_M365_FOLDER)) {
+ ESourceM365Folder *extension;
+ const gchar *folder_id;
+
+ extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_M365_FOLDER);
+ folder_id = e_source_m365_folder_get_id (extension);
+
+ if (folder_id) {
+ EM365Backend *m365_backend = E_M365_BACKEND (backend);
+
+ LOCK (m365_backend);
+ g_hash_table_remove (m365_backend->priv->folder_sources, folder_id);
+ UNLOCK (m365_backend);
+ }
+ }
+
+ /* Chain up to parent's method. */
+ E_COLLECTION_BACKEND_CLASS (e_m365_backend_parent_class)->child_removed (backend, child_source);
+}
+
+static gboolean
+m365_backend_create_resource_sync (ECollectionBackend *backend,
+ ESource *source,
+ GCancellable *cancellable,
+ GError **error)
+{
+#if 0
+ EM365Connection *connection = NULL;
+ M365FolderId *out_folder_id = NULL;
+ EM365FolderType folder_type = E_M365_FOLDER_TYPE_UNKNOWN;
+ const gchar *extension_name;
+ const gchar *parent_folder_id = NULL;
+ gchar *folder_name;
+ gboolean success = FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_M365_FOLDER;
+ if (e_source_has_extension (source, extension_name)) {
+ ESourceM365Folder *extension;
+
+ /* foreign and public folders are just added */
+ extension = e_source_get_extension (source, extension_name);
+ if (e_source_m365_folder_get_foreign (extension) ||
+ e_source_m365_folder_get_public (extension))
+ success = TRUE;
+ }
+
+ if (!success) {
+ connection = e_m365_backend_ref_connection_sync (E_M365_BACKEND (backend), NULL, NULL, NULL,
cancellable, error);
+ if (connection == NULL)
+ return FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+ if (e_source_has_extension (source, extension_name)) {
+ folder_type = E_M365_FOLDER_TYPE_CONTACTS;
+ parent_folder_id = "contacts";
+ }
+
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ if (e_source_has_extension (source, extension_name)) {
+ folder_type = E_M365_FOLDER_TYPE_CALENDAR;
+ parent_folder_id = "calendar";
+ }
+
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ if (e_source_has_extension (source, extension_name)) {
+ folder_type = E_M365_FOLDER_TYPE_TASKS;
+ parent_folder_id = "tasks";
+ }
+
+ /* FIXME No support for memo lists. */
+
+ if (parent_folder_id == NULL) {
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Could not determine a suitable folder "
+ "class for a new folder named ā%sā"),
+ e_source_get_display_name (source));
+ goto exit;
+ }
+
+ folder_name = e_source_dup_display_name (source);
+
+ success = e_m365_connection_create_folder_sync (
+ connection, M365_PRIORITY_MEDIUM,
+ parent_folder_id, TRUE,
+ folder_name, folder_type,
+ &out_folder_id, cancellable, error);
+
+ g_free (folder_name);
+
+ /* Sanity check */
+ g_warn_if_fail (
+ (success && out_folder_id != NULL) ||
+ (!success && out_folder_id == NULL));
+
+ if (out_folder_id != NULL) {
+ ESourceM365Folder *extension;
+ const gchar *extension_name;
+
+ extension_name = E_SOURCE_EXTENSION_M365_FOLDER;
+ extension = e_source_get_extension (source, extension_name);
+ e_source_m365_folder_set_id (
+ extension, out_folder_id->id);
+ e_source_m365_folder_set_change_key (
+ extension, out_folder_id->change_key);
+
+ e_m365_folder_id_free (out_folder_id);
+ }
+ }
+
+ if (success) {
+ ESourceRegistryServer *server;
+ ESource *parent_source;
+ const gchar *cache_dir;
+ const gchar *parent_uid;
+
+ /* Configure the source as a collection member. */
+ parent_source = e_backend_get_source (E_BACKEND (backend));
+ parent_uid = e_source_get_uid (parent_source);
+ e_source_set_parent (source, parent_uid);
+
+ /* Changes should be written back to the cache directory. */
+ cache_dir = e_collection_backend_get_cache_dir (backend);
+ e_server_side_source_set_write_directory (
+ E_SERVER_SIDE_SOURCE (source), cache_dir);
+
+ /* Set permissions for clients. */
+ e_server_side_source_set_writable (E_SERVER_SIDE_SOURCE (source), TRUE);
+ e_server_side_source_set_remote_deletable (E_SERVER_SIDE_SOURCE (source), TRUE);
+
+ server = e_collection_backend_ref_server (backend);
+ e_source_registry_server_add_source (server, source);
+ g_object_unref (server);
+ }
+
+ exit:
+ if (connection)
+ g_object_unref (connection);
+
+ return success;
+#endif
+ return FALSE;
+}
+
+static gboolean
+m365_backend_delete_resource_sync (ECollectionBackend *backend,
+ ESource *source,
+ GCancellable *cancellable,
+ GError **error)
+{
+#if 0
+ EM365Connection *connection;
+ ESourceM365Folder *extension;
+ const gchar *extension_name;
+ gboolean success = FALSE;
+
+ connection = e_m365_backend_ref_connection_sync (E_M365_BACKEND (backend), NULL, NULL, NULL,
cancellable, error);
+ if (connection == NULL)
+ return FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_M365_FOLDER;
+ if (!e_source_has_extension (source, extension_name)) {
+ g_set_error (
+ error, G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Data source ā%sā does not represent a Microsoft 365 folder"),
+ e_source_get_display_name (source));
+ goto exit;
+ }
+ extension = e_source_get_extension (source, extension_name);
+
+ if (e_source_m365_folder_get_foreign (extension) ||
+ e_source_m365_folder_get_public (extension)) {
+ /* do not delete foreign or public folders,
+ * just remove them from local store */
+ success = TRUE;
+ } else {
+ gchar *folder_id;
+
+ folder_id = e_source_m365_folder_dup_id (extension);
+
+ success = e_m365_connection_delete_folder_sync (
+ connection, M365_PRIORITY_MEDIUM, folder_id,
+ FALSE, "HardDelete", cancellable, error);
+
+ g_free (folder_id);
+ }
+
+ if (success)
+ success = e_source_remove_sync (source, cancellable, error);
+
+ exit:
+ g_object_unref (connection);
+
+ return success;
+#endif
+ return FALSE;
+}
+
+static gboolean
+m365_backend_get_destination_address (EBackend *backend,
+ gchar **host,
+ guint16 *port)
+{
+ g_return_val_if_fail (port != NULL, FALSE);
+ g_return_val_if_fail (host != NULL, FALSE);
+
+ *host = g_strdup ("graph.microsoft.com");
+ *port = 443;
+
+ return TRUE;
+}
+
+static ESourceAuthenticationResult
+m365_backend_authenticate_sync (EBackend *backend,
+ const ENamedParameters *credentials,
+ gchar **out_certificate_pem,
+ GTlsCertificateFlags *out_certificate_errors,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelM365Settings *m365_settings;
+ EM365Connection *cnc;
+ ESourceAuthenticationResult result;
+
+ g_return_val_if_fail (E_IS_M365_BACKEND (backend), E_SOURCE_AUTHENTICATION_ERROR);
+
+ m365_settings = camel_m365_settings_get_from_backend (backend, NULL);
+ g_return_val_if_fail (m365_settings != NULL, E_SOURCE_AUTHENTICATION_ERROR);
+
+ cnc = e_m365_connection_new (e_backend_get_source (backend), m365_settings);
+
+ result = e_m365_connection_authenticate_sync (cnc, NULL, E_M365_FOLDER_KIND_UNKNOWN, NULL, NULL,
out_certificate_pem, out_certificate_errors, cancellable, error);
+
+ if (result == E_SOURCE_AUTHENTICATION_ACCEPTED) {
+ e_collection_backend_authenticate_children (E_COLLECTION_BACKEND (backend), credentials);
+ m365_backend_sync_folders (E_M365_BACKEND (backend), cnc, NULL, NULL, NULL);
+ } else if (result == E_SOURCE_AUTHENTICATION_REJECTED &&
+ !e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD)) {
+ result = E_SOURCE_AUTHENTICATION_REQUIRED;
+ }
+
+ g_clear_object (&cnc);
+
+ return result;
+}
+
+static void
+m365_backend_constructed (GObject *object)
+{
+ EBackend *backend;
+ ESource *source;
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_backend_parent_class)->constructed (object);
+
+ backend = E_BACKEND (object);
+ source = e_backend_get_source (backend);
+
+ e_server_side_source_set_remote_creatable (E_SERVER_SIDE_SOURCE (source), TRUE);
+
+ /* Reset the connectable, it steals data from Authentication extension,
+ where is written incorrect address */
+ e_backend_set_connectable (backend, NULL);
+
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION)) {
+ ESourceCollection *collection_extension;
+
+ collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION);
+ e_source_collection_set_allow_sources_rename (collection_extension, TRUE);
+ }
+}
+
+static void
+m365_backend_dispose (GObject *object)
+{
+ EM365Backend *m365_backend = E_M365_BACKEND (object);
+ ESource *source;
+
+ source = e_backend_get_source (E_BACKEND (object));
+ if (source && m365_backend->priv->source_changed_id) {
+ g_signal_handler_disconnect (source, m365_backend->priv->source_changed_id);
+ m365_backend->priv->source_changed_id = 0;
+ }
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_backend_parent_class)->dispose (object);
+}
+
+static void
+m365_backend_finalize (GObject *object)
+{
+ EM365Backend *m365_backend = E_M365_BACKEND (object);
+
+ g_hash_table_destroy (m365_backend->priv->folder_sources);
+ g_mutex_clear (&m365_backend->priv->property_lock);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_m365_backend_parent_class)->finalize (object);
+}
+
+static void
+e_m365_backend_class_init (EM365BackendClass *class)
+{
+ GObjectClass *object_class;
+ EBackendClass *backend_class;
+ ECollectionBackendClass *collection_backend_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = m365_backend_dispose;
+ object_class->finalize = m365_backend_finalize;
+ object_class->constructed = m365_backend_constructed;
+
+ collection_backend_class = E_COLLECTION_BACKEND_CLASS (class);
+ collection_backend_class->populate = m365_backend_populate;
+ collection_backend_class->dup_resource_id = m365_backend_dup_resource_id;
+ collection_backend_class->child_added = m365_backend_child_added;
+ collection_backend_class->child_removed = m365_backend_child_removed;
+ collection_backend_class->create_resource_sync = m365_backend_create_resource_sync;
+ collection_backend_class->delete_resource_sync = m365_backend_delete_resource_sync;
+
+ backend_class = E_BACKEND_CLASS (class);
+ backend_class->get_destination_address = m365_backend_get_destination_address;
+ backend_class->authenticate_sync = m365_backend_authenticate_sync;
+
+ /* This generates an ESourceCamel subtype for CamelM365Settings. */
+ e_source_camel_generate_subtype ("microsoft365", CAMEL_TYPE_M365_SETTINGS);
+}
+
+static void
+e_m365_backend_class_finalize (EM365BackendClass *class)
+{
+}
+
+static void
+e_m365_backend_init (EM365Backend *backend)
+{
+ backend->priv = e_m365_backend_get_instance_private (backend);
+ backend->priv->folder_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
g_object_unref);
+
+ g_mutex_init (&backend->priv->property_lock);
+}
+
+void
+e_m365_backend_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_m365_backend_register_type (type_module);
+}
diff --git a/src/Microsoft365/registry/e-m365-backend.h b/src/Microsoft365/registry/e-m365-backend.h
new file mode 100644
index 00000000..834c28c8
--- /dev/null
+++ b/src/Microsoft365/registry/e-m365-backend.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_M365_BACKEND_H
+#define E_M365_BACKEND_H
+
+#include <libebackend/libebackend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_M365_BACKEND \
+ (e_m365_backend_get_type ())
+#define E_M365_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_M365_BACKEND, EM365Backend))
+#define E_M365_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_M365_BACKEND, EM365BackendClass))
+#define E_IS_M365_BACKEND(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_M365_BACKEND))
+#define E_IS_M365_BACKEND_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_M365_BACKEND))
+#define E_M365_BACKEND_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_M365_BACKEND, EM365BackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EM365Backend EM365Backend;
+typedef struct _EM365BackendClass EM365BackendClass;
+typedef struct _EM365BackendPrivate EM365BackendPrivate;
+
+struct _EM365Backend {
+ ECollectionBackend parent;
+ EM365BackendPrivate *priv;
+};
+
+struct _EM365BackendClass {
+ ECollectionBackendClass parent_class;
+};
+
+GType e_m365_backend_get_type (void) G_GNUC_CONST;
+void e_m365_backend_type_register (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_M365_BACKEND_H */
diff --git a/src/Microsoft365/registry/e-source-m365-deltas.c
b/src/Microsoft365/registry/e-source-m365-deltas.c
new file mode 100644
index 00000000..cd3a6297
--- /dev/null
+++ b/src/Microsoft365/registry/e-source-m365-deltas.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include "e-source-m365-deltas.h"
+
+struct _ESourceM365DeltasPrivate {
+ gchar *contacts_link;
+};
+
+enum {
+ PROP_0,
+ PROP_CONTACTS_LINK
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ESourceM365Deltas, e_source_m365_deltas, E_TYPE_SOURCE_EXTENSION)
+
+static void
+source_m365_deltas_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTACTS_LINK:
+ e_source_m365_deltas_set_contacts_link (
+ E_SOURCE_M365_DELTAS (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_m365_deltas_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CONTACTS_LINK:
+ g_value_take_string (
+ value,
+ e_source_m365_deltas_dup_contacts_link (
+ E_SOURCE_M365_DELTAS (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_m365_deltas_finalize (GObject *object)
+{
+ ESourceM365Deltas *m365_deltas = E_SOURCE_M365_DELTAS (object);
+
+ g_free (m365_deltas->priv->contacts_link);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_source_m365_deltas_parent_class)->finalize (object);
+}
+
+static void
+e_source_m365_deltas_class_init (ESourceM365DeltasClass *class)
+{
+ GObjectClass *object_class;
+ ESourceExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = source_m365_deltas_set_property;
+ object_class->get_property = source_m365_deltas_get_property;
+ object_class->finalize = source_m365_deltas_finalize;
+
+ extension_class = E_SOURCE_EXTENSION_CLASS (class);
+ extension_class->name = E_SOURCE_EXTENSION_M365_DELTAS;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CONTACTS_LINK,
+ g_param_spec_string (
+ "contacts-link",
+ "Contacts Link",
+ "The delta link for contacts",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS |
+ E_SOURCE_PARAM_SETTING));
+}
+
+static void
+e_source_m365_deltas_init (ESourceM365Deltas *extension)
+{
+ extension->priv = e_source_m365_deltas_get_instance_private (extension);
+}
+
+void
+e_source_m365_deltas_type_register (GTypeModule *type_module)
+{
+ /* We need to ensure this is registered, because it's looked up
+ * by name in e_source_get_extension(). */
+ g_type_ensure (E_TYPE_SOURCE_M365_DELTAS);
+}
+
+const gchar *
+e_source_m365_deltas_get_contacts_link (ESourceM365Deltas *extension)
+{
+ g_return_val_if_fail (E_IS_SOURCE_M365_DELTAS (extension), NULL);
+
+ return extension->priv->contacts_link;
+}
+
+gchar *
+e_source_m365_deltas_dup_contacts_link (ESourceM365Deltas *extension)
+{
+ const gchar *protected;
+ gchar *duplicate;
+
+ g_return_val_if_fail (E_IS_SOURCE_M365_DELTAS (extension), NULL);
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ protected = e_source_m365_deltas_get_contacts_link (extension);
+ duplicate = g_strdup (protected);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ return duplicate;
+}
+
+void
+e_source_m365_deltas_set_contacts_link (ESourceM365Deltas *extension,
+ const gchar *delta_link)
+{
+ g_return_if_fail (E_IS_SOURCE_M365_DELTAS (extension));
+
+ e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
+
+ if (g_strcmp0 (extension->priv->contacts_link, delta_link) == 0) {
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+ return;
+ }
+
+ g_free (extension->priv->contacts_link);
+ extension->priv->contacts_link = e_util_strdup_strip (delta_link);
+
+ e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
+
+ g_object_notify (G_OBJECT (extension), "contacts-link");
+}
diff --git a/src/Microsoft365/registry/e-source-m365-deltas.h
b/src/Microsoft365/registry/e-source-m365-deltas.h
new file mode 100644
index 00000000..3cd9132e
--- /dev/null
+++ b/src/Microsoft365/registry/e-source-m365-deltas.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_SOURCE_M365_DELTAS_H
+#define E_SOURCE_M365_DELTAS_H
+
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_M365_DELTAS \
+ (e_source_m365_deltas_get_type ())
+#define E_SOURCE_M365_DELTAS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SOURCE_M365_DELTAS, ESourceM365Deltas))
+#define E_SOURCE_M365_DELTAS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SOURCE_M365_DELTAS, ESourceM365DeltasClass))
+#define E_IS_SOURCE_M365_DELTAS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SOURCE_M365_DELTAS))
+#define E_IS_SOURCE_M365_DELTAS_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SOURCE_M365_DELTAS))
+#define E_SOURCE_M365_DELTAS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SOURCE_M365_DELTAS, ESourceM365DeltasClass))
+
+#define E_SOURCE_EXTENSION_M365_DELTAS "Microsoft365 Deltas"
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceM365Deltas ESourceM365Deltas;
+typedef struct _ESourceM365DeltasClass ESourceM365DeltasClass;
+typedef struct _ESourceM365DeltasPrivate ESourceM365DeltasPrivate;
+
+struct _ESourceM365Deltas {
+ ESourceExtension parent;
+ ESourceM365DeltasPrivate *priv;
+};
+
+struct _ESourceM365DeltasClass {
+ ESourceExtensionClass parent_class;
+};
+
+GType e_source_m365_deltas_get_type (void) G_GNUC_CONST;
+void e_source_m365_deltas_type_register
+ (GTypeModule *type_module);
+const gchar * e_source_m365_deltas_get_contacts_link
+ (ESourceM365Deltas *extension);
+gchar * e_source_m365_deltas_dup_contacts_link
+ (ESourceM365Deltas *extension);
+void e_source_m365_deltas_set_contacts_link
+ (ESourceM365Deltas *extension,
+ const gchar *delta_link);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_M365_DELTAS_H */
diff --git a/src/Microsoft365/registry/module-m365-backend.c b/src/Microsoft365/registry/module-m365-backend.c
new file mode 100644
index 00000000..554501d2
--- /dev/null
+++ b/src/Microsoft365/registry/module-m365-backend.c
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2020 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "common/e-oauth2-service-microsoft365.h"
+#include "common/e-source-m365-folder.h"
+
+#include "e-m365-backend.h"
+#include "e-m365-backend-factory.h"
+#include "e-source-m365-deltas.h"
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, M365_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ e_oauth2_service_microsoft365_type_register (type_module);
+ e_source_m365_folder_type_register (type_module);
+ e_source_m365_deltas_type_register (type_module);
+
+ if (g_strcmp0 (g_getenv ("ENABLE_M365"), "1") == 0) {
+ e_m365_backend_type_register (type_module);
+ e_m365_backend_factory_type_register (type_module);
+ }
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 50a9b020..35089dd8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -32,8 +32,8 @@ macro(add_ews_test _name)
target_include_directories(${_name} PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}
- ${CMAKE_BINARY_DIR}/src
- ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
${LIBECAL_INCLUDE_DIRS}
${LIBEDATASERVER_INCLUDE_DIRS}
${UHTTPMOCK_INCLUDE_DIRS}
diff --git a/tests/ews-test-camel.c b/tests/ews-test-camel.c
index 8e9cd866..3e84ca60 100644
--- a/tests/ews-test-camel.c
+++ b/tests/ews-test-camel.c
@@ -7,9 +7,9 @@
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
-#include "server/e-ews-connection.h"
-#include "server/e-ews-debug.h"
-#include "server/e-ews-folder.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-debug.h"
+#include "common/e-ews-folder.h"
#include "ews-test-common.h"
diff --git a/tests/ews-test-common.h b/tests/ews-test-common.h
index 663a3049..dd55a623 100644
--- a/tests/ews-test-common.h
+++ b/tests/ews-test-common.h
@@ -10,8 +10,8 @@
#include <uhttpmock/uhm.h>
-#include "server/camel-ews-settings.h"
-#include "server/e-ews-connection.h"
+#include "common/camel-ews-settings.h"
+#include "common/e-ews-connection.h"
G_BEGIN_DECLS
diff --git a/tests/ews-test-timezones.c b/tests/ews-test-timezones.c
index 76646045..8fea8280 100644
--- a/tests/ews-test-timezones.c
+++ b/tests/ews-test-timezones.c
@@ -6,8 +6,8 @@
*/
#include "calendar/e-cal-backend-ews-utils.h"
-#include "server/e-ews-connection.h"
-#include "server/e-ews-debug.h"
+#include "common/e-ews-connection.h"
+#include "common/e-ews-debug.h"
#include "ews-test-common.h"
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]