@@ -3,10 +3,13 @@ package com.github.choonchernlim.calsync.core
33import com.github.choonchernlim.calsync.exchange.ExchangeEvent
44import com.google.api.client.util.DateTime
55import com.google.api.services.calendar.model.Event
6+ import com.google.api.services.calendar.model.EventAttendee
67import com.google.api.services.calendar.model.EventDateTime
78import com.google.api.services.calendar.model.EventReminder
9+ import microsoft.exchange.webservices.data.core.enumeration.property.LegacyFreeBusyStatus
810import microsoft.exchange.webservices.data.core.enumeration.property.MeetingResponseType
911import microsoft.exchange.webservices.data.core.service.item.Appointment
12+ import microsoft.exchange.webservices.data.property.complex.AttendeeCollection
1013import microsoft.exchange.webservices.data.property.complex.MessageBody
1114import org.apache.commons.lang3.StringEscapeUtils
1215import org.joda.time.format.DateTimeFormat
@@ -21,15 +24,6 @@ import org.jsoup.safety.Whitelist
2124class Mapper {
2225 static final DateTimeFormatter dateTimeFormatter = DateTimeFormat . forPattern(" MMM dd '@' hh:mm a" )
2326
24- private static final Map<MeetingResponseType , String > MY_RESPONSE_TYPE = [
25- (MeetingResponseType.Accept ) : ' ACCEPTED' ,
26- (MeetingResponseType.Decline ) : ' DECLINED' ,
27- (MeetingResponseType.NoResponseReceived ): ' UNRESPONDED' ,
28- (MeetingResponseType.Tentative ) : ' TENTATIVE' ,
29- (MeetingResponseType.Organizer ) : ' ORGANIZER' ,
30- (MeetingResponseType.Unknown ) : ' UNKNOWN' ,
31- ]
32-
3327 /**
3428 * Maps Google EventDateTime to Joda DateTime.
3529 *
@@ -97,6 +91,26 @@ class Mapper {
9791 static CalSyncEvent toCalSyncEvent (Event event ) {
9892 assert event
9993
94+ def attendees = event. getAttendees()
95+ .collect {
96+ def response = CalSyncEvent.Attendee.Response . NO_RESPONSE
97+ if (it. getResponseStatus() == " accepted" ) {
98+ response = CalSyncEvent.Attendee.Response . ACCEPTED
99+ } else if (it. getResponseStatus() == " declined" ) {
100+ response = CalSyncEvent.Attendee.Response . DECLINED
101+ } else if (it. getResponseStatus() == " tentative" ) {
102+ response = CalSyncEvent.Attendee.Response . TENTATIVE
103+ }
104+
105+ new CalSyncEvent.Attendee (
106+ address : it. getEmail(). toLowerCase(), // address might be lowercased by Google
107+ name : it. getDisplayName(),
108+ response : response,
109+ isOptional : it. isOptional()
110+ )
111+ }
112+ .sort { it. address } // sort because Google might return attendees in a different order
113+
100114 return new CalSyncEvent (
101115 googleEventId : event. getId(),
102116 startDateTime : toJodaDateTime(event. getStart()),
@@ -105,7 +119,11 @@ class Mapper {
105119 location : event. getLocation(),
106120 reminderMinutesBeforeStart : event. getReminders()?. getOverrides()?. get(0 )?. getMinutes(),
107121 body : event. getDescription() ?: null ,
108- isAllDayEvent : isAllDayEvent(event)
122+ isAllDayEvent : isAllDayEvent(event),
123+ attendees : attendees,
124+ organizerAddress : event. getOrganizer(). getEmail(). toLowerCase(), // address might be lowercased by Google
125+ organizerName : event. getOrganizer(). getDisplayName(),
126+ isBusy : event. getTransparency() != " transparent"
109127 )
110128 }
111129
@@ -121,25 +139,68 @@ class Mapper {
121139 return event. getStart(). getDate() && event. getEnd(). getDate()
122140 }
123141
142+ static List<ExchangeEvent.Attendee > toExchangeAttendeeList (AttendeeCollection attendeeCollection ) {
143+ assert attendeeCollection
144+
145+ return attendeeCollection. collect {
146+ new ExchangeEvent.Attendee (
147+ address : it. address,
148+ name : it. name,
149+ response : it. responseType. name()
150+ )
151+ }
152+ }
153+
154+ static CalSyncEvent.Attendee toCalSyncAttendee (ExchangeEvent.Attendee attendee , Boolean optional ) {
155+ assert attendee
156+
157+ def response = CalSyncEvent.Attendee.Response . NO_RESPONSE
158+ if (attendee. response == MeetingResponseType.Accept . name()) {
159+ response = CalSyncEvent.Attendee.Response . ACCEPTED
160+ } else if (attendee. response == MeetingResponseType.Decline . name()) {
161+ response = CalSyncEvent.Attendee.Response . DECLINED
162+ } else if (attendee. response == MeetingResponseType.Tentative . name()) {
163+ response = CalSyncEvent.Attendee.Response . TENTATIVE
164+ }
165+
166+ new CalSyncEvent.Attendee (
167+ address : attendee. address. toLowerCase(), // address might be lowercased by Google
168+ name : attendee. name,
169+ response : response,
170+ isOptional : optional
171+ )
172+ }
173+
124174 /**
125175 * Maps Exchange Event to CalSyncEvent.
126176 *
127177 * @param exchangeEvent Exchange Event
128178 * @param includeEventBody Whether to include event body or not
129179 * @return CalSyncEvent
130180 */
131- static CalSyncEvent toCalSyncEvent (ExchangeEvent exchangeEvent , Boolean includeEventBody ) {
181+ static CalSyncEvent toCalSyncEvent (ExchangeEvent exchangeEvent , Boolean includeEventBody , Boolean includeAttendees ) {
132182 assert exchangeEvent
133183 assert includeEventBody != null
184+ assert includeAttendees != null
185+
186+ def attendees = includeAttendees ? exchangeEvent. requiredAttendees. collect {
187+ toCalSyncAttendee(it, false )
188+ } + exchangeEvent. optionalAttendees. collect {
189+ toCalSyncAttendee(it, true )
190+ }. sort { it. address } : null // sort because Google might return attendees in a different order
134191
135192 return new CalSyncEvent (
136193 startDateTime : exchangeEvent. startDateTime,
137194 endDateTime : exchangeEvent. endDateTime,
138195 subject : exchangeEvent. subject,
139196 location : exchangeEvent. location,
140- reminderMinutesBeforeStart : exchangeEvent. reminderMinutesBeforeStart,
197+ reminderMinutesBeforeStart : exchangeEvent. isReminderSet ? exchangeEvent . reminderMinutesBeforeStart : null ,
141198 body : includeEventBody ? exchangeEvent. body : null ,
142- isAllDayEvent : exchangeEvent. isAllDayEvent
199+ isAllDayEvent : exchangeEvent. isAllDayEvent,
200+ attendees : attendees,
201+ organizerAddress : exchangeEvent. organizerAddress. toLowerCase(), // address might be lowercased by Google
202+ organizerName : exchangeEvent. organizerName,
203+ isBusy : exchangeEvent. isBusy
143204 )
144205 }
145206
@@ -164,14 +225,46 @@ class Mapper {
164225 ]
165226 ) : null
166227
228+ def attendees = calSyncEvent. attendees. collect {
229+ def isOrganizer = it. address == calSyncEvent. organizerAddress
230+
231+ def response = " needsAction"
232+ if (it. response == CalSyncEvent.Attendee.Response . ACCEPTED ) {
233+ response = " accepted"
234+ } else if (it. response == CalSyncEvent.Attendee.Response . DECLINED ) {
235+ response = " declined"
236+ } else if (it. response == CalSyncEvent.Attendee.Response . TENTATIVE ) {
237+ response = " tentative"
238+ }
239+
240+ def name = it. name
241+ if (isOrganizer) {
242+ name = " $name (Organizer)"
243+ }
244+
245+ new EventAttendee (
246+ email : it. address,
247+ displayName : name,
248+ responseStatus : response,
249+ optional : it. isOptional,
250+ organizer : isOrganizer
251+ )
252+ }
253+
167254 return new Event (
168255 id : calSyncEvent. googleEventId,
169256 start : toGoogleEventDateTime(calSyncEvent. isAllDayEvent, calSyncEvent. startDateTime),
170257 end : toGoogleEventDateTime(calSyncEvent. isAllDayEvent, calSyncEvent. endDateTime),
171258 summary : calSyncEvent. subject,
172259 location : calSyncEvent. location,
173260 reminders : reminders,
174- description : calSyncEvent. body
261+ description : calSyncEvent. body,
262+ attendees : attendees,
263+ transparency : calSyncEvent. isBusy ? " opaque" : " transparent" ,
264+ organizer : new Event.Organizer (
265+ email : calSyncEvent. organizerAddress,
266+ displayName : calSyncEvent. organizerName
267+ )
175268 )
176269 }
177270
@@ -188,12 +281,18 @@ class Mapper {
188281 return new ExchangeEvent (
189282 startDateTime : new org.joda.time.DateTime (appointment. start),
190283 endDateTime : new org.joda.time.DateTime (appointment. end),
191- subject : " ${ MY_RESPONSE_TYPE[ appointment.myResponseType] } - ${ appointment. subject} " ,
284+ subject : appointment. subject,
192285 location : appointment. location,
286+ isReminderSet : appointment. isReminderSet,
193287 reminderMinutesBeforeStart : appointment. reminderMinutesBeforeStart,
194288 body : toPlainText(MessageBody . getStringFromMessageBody(appointment. body)),
195289 isCanceled : appointment. isCancelled,
196- isAllDayEvent : appointment. isAllDayEvent
290+ isAllDayEvent : appointment. isAllDayEvent,
291+ optionalAttendees : toExchangeAttendeeList(appointment. optionalAttendees),
292+ requiredAttendees : toExchangeAttendeeList(appointment. requiredAttendees),
293+ organizerAddress : appointment. organizer. address,
294+ organizerName : appointment. organizer. name,
295+ isBusy : appointment. legacyFreeBusyStatus == LegacyFreeBusyStatus.Busy
197296 )
198297 }
199298
0 commit comments