2010-07-29 17:18:59 -04:00
|
|
|
/*
|
|
|
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
|
|
|
* Copyright (C) 2010 Mickael Guessant
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
package davmail.exchange;
|
|
|
|
|
2010-07-29 20:04:54 -04:00
|
|
|
import davmail.Settings;
|
2010-07-31 18:52:45 -04:00
|
|
|
import davmail.util.StringUtil;
|
2010-07-30 11:09:44 -04:00
|
|
|
import org.apache.log4j.Logger;
|
2010-07-29 20:04:54 -04:00
|
|
|
|
2010-07-31 18:52:45 -04:00
|
|
|
import java.io.*;
|
2010-09-04 03:35:33 -04:00
|
|
|
import java.text.ParseException;
|
|
|
|
import java.text.SimpleDateFormat;
|
2010-12-12 16:39:19 -05:00
|
|
|
import java.util.*;
|
2010-07-29 17:18:59 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* VCalendar object.
|
|
|
|
*/
|
|
|
|
public class VCalendar extends VObject {
|
2010-07-30 11:09:44 -04:00
|
|
|
protected static final Logger LOGGER = Logger.getLogger(VCalendar.class);
|
2010-07-29 17:18:59 -04:00
|
|
|
protected VObject firstVevent;
|
|
|
|
protected VObject vTimezone;
|
|
|
|
protected String email;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create VCalendar object from reader;
|
|
|
|
*
|
2010-07-30 11:09:44 -04:00
|
|
|
* @param reader stream reader
|
|
|
|
* @param email current user email
|
|
|
|
* @param vTimezone user OWA timezone
|
2010-07-29 17:18:59 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-07-30 11:09:44 -04:00
|
|
|
public VCalendar(BufferedReader reader, String email, VObject vTimezone) throws IOException {
|
2010-07-29 17:18:59 -04:00
|
|
|
super(reader);
|
|
|
|
if (!"VCALENDAR".equals(type)) {
|
|
|
|
throw new IOException("Invalid type: " + type);
|
|
|
|
}
|
|
|
|
this.email = email;
|
2010-07-30 11:09:44 -04:00
|
|
|
// set OWA timezone information
|
2010-09-16 17:33:20 -04:00
|
|
|
if (this.vTimezone == null && vTimezone != null) {
|
2011-07-18 06:24:36 -04:00
|
|
|
setTimezone(vTimezone);
|
2010-07-30 11:09:44 -04:00
|
|
|
}
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-07-30 11:09:44 -04:00
|
|
|
* Create VCalendar object from string;
|
2010-07-29 17:18:59 -04:00
|
|
|
*
|
|
|
|
* @param vCalendarBody item body
|
2010-07-29 18:30:38 -04:00
|
|
|
* @param email current user email
|
2010-07-30 11:09:44 -04:00
|
|
|
* @param vTimezone user OWA timezone
|
2010-07-29 17:18:59 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-07-30 11:09:44 -04:00
|
|
|
public VCalendar(String vCalendarBody, String email, VObject vTimezone) throws IOException {
|
|
|
|
this(new ICSBufferedReader(new StringReader(vCalendarBody)), email, vTimezone);
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
|
2010-07-31 18:52:45 -04:00
|
|
|
/**
|
|
|
|
* Create VCalendar object from string;
|
|
|
|
*
|
|
|
|
* @param vCalendarContent item content
|
|
|
|
* @param email current user email
|
|
|
|
* @param vTimezone user OWA timezone
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public VCalendar(byte[] vCalendarContent, String email, VObject vTimezone) throws IOException {
|
|
|
|
this(new ICSBufferedReader(new InputStreamReader(new ByteArrayInputStream(vCalendarContent), "UTF-8")), email, vTimezone);
|
|
|
|
}
|
|
|
|
|
2010-09-13 12:07:49 -04:00
|
|
|
/**
|
|
|
|
* Empty constructor
|
|
|
|
*/
|
|
|
|
public VCalendar() {
|
2010-09-15 18:49:19 -04:00
|
|
|
type = "VCALENDAR";
|
2010-09-13 12:07:49 -04:00
|
|
|
}
|
|
|
|
|
2011-07-18 06:24:36 -04:00
|
|
|
/**
|
|
|
|
* Set timezone on vObject
|
|
|
|
*
|
|
|
|
* @param vTimezone timezone object
|
|
|
|
*/
|
|
|
|
public void setTimezone(VObject vTimezone) {
|
|
|
|
if (vObjects == null) {
|
|
|
|
addVObject(vTimezone);
|
|
|
|
} else {
|
|
|
|
vObjects.add(0, vTimezone);
|
|
|
|
}
|
|
|
|
this.vTimezone = vTimezone;
|
|
|
|
}
|
|
|
|
|
2010-07-29 17:18:59 -04:00
|
|
|
@Override
|
2010-09-13 12:07:49 -04:00
|
|
|
public void addVObject(VObject vObject) {
|
2010-07-29 17:18:59 -04:00
|
|
|
super.addVObject(vObject);
|
2011-07-18 06:24:36 -04:00
|
|
|
if (firstVevent == null && ("VEVENT".equals(vObject.type) || "VTODO".equals(vObject.type))) {
|
2010-07-29 17:18:59 -04:00
|
|
|
firstVevent = vObject;
|
|
|
|
}
|
|
|
|
if ("VTIMEZONE".equals(vObject.type)) {
|
|
|
|
vTimezone = vObject;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected boolean isAllDay(VObject vObject) {
|
|
|
|
VProperty dtstart = vObject.getProperty("DTSTART");
|
2010-07-29 18:30:38 -04:00
|
|
|
return dtstart != null && dtstart.hasParam("VALUE", "DATE");
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected boolean isCdoAllDay(VObject vObject) {
|
|
|
|
return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Check if vCalendar is CDO allday.
|
|
|
|
*
|
|
|
|
* @return true if vCalendar has X-MICROSOFT-CDO-ALLDAYEVENT property set to TRUE
|
|
|
|
*/
|
2010-09-17 06:13:00 -04:00
|
|
|
public boolean isCdoAllDay() {
|
|
|
|
return firstVevent != null && isCdoAllDay(firstVevent);
|
|
|
|
}
|
|
|
|
|
2012-03-20 19:08:42 -04:00
|
|
|
public String getEmailValue(VProperty property) {
|
2010-07-31 18:52:45 -04:00
|
|
|
if (property == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
String propertyValue = property.getValue();
|
|
|
|
if (propertyValue != null && (propertyValue.startsWith("MAILTO:") || propertyValue.startsWith("mailto:"))) {
|
|
|
|
return propertyValue.substring(7);
|
2010-07-29 17:18:59 -04:00
|
|
|
} else {
|
2010-07-31 18:52:45 -04:00
|
|
|
return propertyValue;
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getMethod() {
|
|
|
|
return getPropertyValue("METHOD");
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void fixVCalendar(boolean fromServer) {
|
2010-09-04 03:35:33 -04:00
|
|
|
// set iCal 4 global X-CALENDARSERVER-ACCESS from CLASS
|
2010-07-29 17:18:59 -04:00
|
|
|
if (fromServer) {
|
|
|
|
setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
|
|
|
|
}
|
|
|
|
|
2010-07-31 18:52:45 -04:00
|
|
|
// iCal 4 global X-CALENDARSERVER-ACCESS
|
2010-07-29 17:18:59 -04:00
|
|
|
String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
|
2010-07-31 18:52:45 -04:00
|
|
|
String now = ExchangeSession.getZuluDateFormat().format(new Date());
|
2010-07-29 17:18:59 -04:00
|
|
|
|
2010-08-17 10:27:07 -04:00
|
|
|
// fix method from iPhone
|
|
|
|
if (!fromServer && getPropertyValue("METHOD") == null) {
|
|
|
|
setPropertyValue("METHOD", "PUBLISH");
|
|
|
|
}
|
|
|
|
|
2010-12-12 16:39:19 -05:00
|
|
|
// rename TZID for maximum iCal/iPhone compatibility
|
|
|
|
String tzid = null;
|
|
|
|
if (fromServer) {
|
|
|
|
// get current tzid
|
2011-06-08 15:58:29 -04:00
|
|
|
VObject vObject = vTimezone;
|
2010-12-12 16:39:19 -05:00
|
|
|
if (vObject != null) {
|
2011-01-02 17:12:41 -05:00
|
|
|
String currentTzid = vObject.getPropertyValue("TZID");
|
2011-01-06 04:33:36 -05:00
|
|
|
// fix TZID with \n (Exchange 2010 bug)
|
|
|
|
if (currentTzid != null && currentTzid.endsWith("\n")) {
|
|
|
|
currentTzid = currentTzid.substring(0, currentTzid.length() - 1);
|
|
|
|
vObject.setPropertyValue("TZID", currentTzid);
|
|
|
|
}
|
2011-01-02 17:12:41 -05:00
|
|
|
if (currentTzid != null && currentTzid.indexOf(' ') >= 0) {
|
|
|
|
try {
|
|
|
|
tzid = ResourceBundle.getBundle("timezones").getString(currentTzid);
|
2011-01-06 04:33:36 -05:00
|
|
|
vObject.setPropertyValue("TZID", tzid);
|
2011-01-02 17:12:41 -05:00
|
|
|
} catch (MissingResourceException e) {
|
2011-01-06 04:33:36 -05:00
|
|
|
LOGGER.debug("Timezone " + currentTzid + " not found in rename table");
|
2011-01-02 17:12:41 -05:00
|
|
|
}
|
2010-12-12 16:39:19 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-24 18:24:38 -04:00
|
|
|
if (!fromServer) {
|
|
|
|
fixTimezone();
|
|
|
|
}
|
|
|
|
|
2010-07-29 17:18:59 -04:00
|
|
|
// iterate over vObjects
|
|
|
|
for (VObject vObject : vObjects) {
|
|
|
|
if ("VEVENT".equals(vObject.type)) {
|
|
|
|
if (calendarServerAccess != null) {
|
2010-07-29 18:30:38 -04:00
|
|
|
vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess));
|
2010-07-31 18:52:45 -04:00
|
|
|
// iCal 3, get X-CALENDARSERVER-ACCESS from local VEVENT
|
2010-07-29 18:30:38 -04:00
|
|
|
} else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) {
|
|
|
|
vObject.setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
2010-07-30 11:09:44 -04:00
|
|
|
if (fromServer) {
|
|
|
|
// remove organizer line for event without attendees for iPhone
|
2010-08-16 11:02:33 -04:00
|
|
|
if (vObject.getProperty("ATTENDEE") == null) {
|
2010-07-30 11:09:44 -04:00
|
|
|
vObject.setPropertyValue("ORGANIZER", null);
|
|
|
|
}
|
|
|
|
// detect allday and update date properties
|
|
|
|
if (isCdoAllDay(vObject)) {
|
|
|
|
setClientAllday(vObject.getProperty("DTSTART"));
|
|
|
|
setClientAllday(vObject.getProperty("DTEND"));
|
|
|
|
}
|
2010-07-31 18:52:45 -04:00
|
|
|
String cdoBusyStatus = vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS");
|
|
|
|
if (cdoBusyStatus != null) {
|
|
|
|
vObject.setPropertyValue("TRANSP",
|
|
|
|
!"FREE".equals(cdoBusyStatus) ? "OPAQUE" : "TRANSPARENT");
|
|
|
|
}
|
2010-07-30 11:09:44 -04:00
|
|
|
|
|
|
|
// Apple iCal doesn't understand this key, and it's entourage
|
|
|
|
// specific (i.e. not needed by any caldav client): strip it out
|
|
|
|
vObject.removeProperty("X-ENTOURAGE_UUID");
|
2010-07-31 18:52:45 -04:00
|
|
|
|
|
|
|
splitExDate(vObject);
|
2010-09-14 18:12:55 -04:00
|
|
|
|
|
|
|
// remove empty properties
|
|
|
|
if ("".equals(vObject.getPropertyValue("LOCATION"))) {
|
|
|
|
vObject.removeProperty("LOCATION");
|
|
|
|
}
|
|
|
|
if ("".equals(vObject.getPropertyValue("DESCRIPTION"))) {
|
|
|
|
vObject.removeProperty("DESCRIPTION");
|
|
|
|
}
|
|
|
|
if ("".equals(vObject.getPropertyValue("CLASS"))) {
|
|
|
|
vObject.removeProperty("CLASS");
|
|
|
|
}
|
2010-12-12 16:39:19 -05:00
|
|
|
// rename TZID
|
|
|
|
if (tzid != null) {
|
|
|
|
VProperty dtStart = vObject.getProperty("DTSTART");
|
|
|
|
if (dtStart != null && dtStart.getParam("TZID") != null) {
|
|
|
|
dtStart.setParam("TZID", tzid);
|
|
|
|
}
|
|
|
|
VProperty dtEnd = vObject.getProperty("DTEND");
|
|
|
|
if (dtEnd != null && dtStart.getParam("TZID") != null) {
|
|
|
|
dtEnd.setParam("TZID", tzid);
|
|
|
|
}
|
|
|
|
}
|
2010-07-30 11:09:44 -04:00
|
|
|
} else {
|
2010-07-29 18:30:38 -04:00
|
|
|
// add organizer line to all events created in Exchange for active sync
|
2010-07-31 18:52:45 -04:00
|
|
|
String organizer = getEmailValue(vObject.getProperty("ORGANIZER"));
|
|
|
|
if (organizer == null) {
|
2010-07-29 18:30:38 -04:00
|
|
|
vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email);
|
2010-07-31 18:52:45 -04:00
|
|
|
} else if (!email.equalsIgnoreCase(organizer) && vObject.getProperty("X-MICROSOFT-CDO-REPLYTIME") == null) {
|
|
|
|
vObject.setPropertyValue("X-MICROSOFT-CDO-REPLYTIME", now);
|
2010-07-29 18:30:38 -04:00
|
|
|
}
|
|
|
|
// set OWA allday flag
|
|
|
|
vObject.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE");
|
2010-07-29 20:04:54 -04:00
|
|
|
vObject.setPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS",
|
|
|
|
!"TRANSPARENT".equals(vObject.getPropertyValue("TRANSP")) ? "BUSY" : "FREE");
|
2010-07-30 11:09:44 -04:00
|
|
|
|
|
|
|
if (isAllDay(vObject)) {
|
|
|
|
// convert date values to outlook compatible values
|
|
|
|
setServerAllday(vObject.getProperty("DTSTART"));
|
|
|
|
setServerAllday(vObject.getProperty("DTEND"));
|
2010-08-17 11:19:09 -04:00
|
|
|
} else {
|
|
|
|
fixTzid(vObject.getProperty("DTSTART"));
|
|
|
|
fixTzid(vObject.getProperty("DTEND"));
|
2010-07-29 18:30:38 -04:00
|
|
|
}
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
2010-07-29 20:04:54 -04:00
|
|
|
|
|
|
|
fixAttendees(vObject, fromServer);
|
|
|
|
|
|
|
|
fixAlarm(vObject, fromServer);
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2011-10-24 18:24:38 -04:00
|
|
|
private void fixTimezone() {
|
|
|
|
if (vTimezone != null && vTimezone.vObjects != null && vTimezone.vObjects.size() > 2) {
|
|
|
|
VObject standard = null;
|
|
|
|
VObject daylight = null;
|
|
|
|
for (VObject vObject:vTimezone.vObjects) {
|
|
|
|
if ("STANDARD".equals(vObject.type)) {
|
|
|
|
if (standard == null ||
|
|
|
|
(vObject.getPropertyValue("DTSTART").compareTo(standard.getPropertyValue("DTSTART")) > 0)) {
|
|
|
|
standard = vObject;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ("DAYLIGHT".equals(vObject.type)) {
|
|
|
|
if (daylight == null ||
|
|
|
|
(vObject.getPropertyValue("DTSTART").compareTo(daylight.getPropertyValue("DTSTART")) > 0)) {
|
|
|
|
daylight = vObject;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vTimezone.vObjects.clear();
|
|
|
|
vTimezone.vObjects.add(standard);
|
|
|
|
vTimezone.vObjects.add(daylight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-08-17 11:19:09 -04:00
|
|
|
private void fixTzid(VProperty property) {
|
|
|
|
if (property != null && !property.hasParam("TZID")) {
|
|
|
|
property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-31 18:52:45 -04:00
|
|
|
protected void splitExDate(VObject vObject) {
|
|
|
|
List<VProperty> exDateProperties = vObject.getProperties("EXDATE");
|
|
|
|
if (exDateProperties != null) {
|
|
|
|
for (VProperty property : exDateProperties) {
|
|
|
|
String value = property.getValue();
|
|
|
|
if (value.indexOf(',') >= 0) {
|
|
|
|
// split property
|
|
|
|
vObject.removeProperty(property);
|
|
|
|
for (String singleValue : value.split(",")) {
|
|
|
|
VProperty singleProperty = new VProperty("EXDATE", singleValue);
|
|
|
|
singleProperty.setParams(property.getParams());
|
|
|
|
vObject.addProperty(singleProperty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void setServerAllday(VProperty property) {
|
2010-09-16 17:33:20 -04:00
|
|
|
if (vTimezone != null) {
|
|
|
|
// set TZID param
|
|
|
|
if (!property.hasParam("TZID")) {
|
|
|
|
property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
|
|
|
|
}
|
|
|
|
// remove VALUE
|
|
|
|
property.removeParam("VALUE");
|
|
|
|
String value = property.getValue();
|
|
|
|
if (value.length() != 8) {
|
|
|
|
LOGGER.warn("Invalid date value in allday event: " + value);
|
|
|
|
}
|
|
|
|
property.setValue(property.getValue() + "T000000");
|
2010-07-30 11:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void setClientAllday(VProperty property) {
|
2010-10-05 18:23:35 -04:00
|
|
|
if (property != null) {
|
|
|
|
// set VALUE=DATE param
|
|
|
|
if (!property.hasParam("VALUE")) {
|
|
|
|
property.addParam("VALUE", "DATE");
|
|
|
|
}
|
|
|
|
// remove TZID
|
|
|
|
property.removeParam("TZID");
|
|
|
|
String value = property.getValue();
|
|
|
|
if (value.length() != 8) {
|
|
|
|
// try to convert datetime value to date value
|
|
|
|
try {
|
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
|
SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
|
|
|
|
calendar.setTime(dateParser.parse(value));
|
|
|
|
calendar.add(Calendar.HOUR_OF_DAY, 12);
|
|
|
|
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
|
|
|
|
value = dateFormatter.format(calendar.getTime());
|
|
|
|
} catch (ParseException e) {
|
|
|
|
LOGGER.warn("Invalid date value in allday event: " + value);
|
|
|
|
}
|
2010-09-04 03:35:33 -04:00
|
|
|
}
|
2010-10-05 18:23:35 -04:00
|
|
|
property.setValue(value);
|
2010-07-30 11:09:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void fixAlarm(VObject vObject, boolean fromServer) {
|
|
|
|
if (vObject.vObjects != null) {
|
|
|
|
for (VObject vAlarm : vObject.vObjects) {
|
|
|
|
if ("VALARM".equals(vAlarm.type)) {
|
|
|
|
String action = vAlarm.getPropertyValue("ACTION");
|
|
|
|
if (fromServer && "DISPLAY".equals(action)
|
|
|
|
// convert DISPLAY to AUDIO only if user defined an alarm sound
|
|
|
|
&& Settings.getProperty("davmail.caldavAlarmSound") != null) {
|
|
|
|
// Convert alarm to audio for iCal
|
|
|
|
vAlarm.setPropertyValue("ACTION", "AUDIO");
|
2010-07-29 20:04:54 -04:00
|
|
|
|
2010-07-30 11:09:44 -04:00
|
|
|
if (vAlarm.getPropertyValue("ATTACH") == null) {
|
|
|
|
// Add defined sound into the audio alarm
|
|
|
|
VProperty vProperty = new VProperty("ATTACH", Settings.getProperty("davmail.caldavAlarmSound"));
|
|
|
|
vProperty.addParam("VALUE", "URI");
|
|
|
|
vAlarm.addProperty(vProperty);
|
|
|
|
}
|
2010-07-29 20:04:54 -04:00
|
|
|
|
2010-07-30 11:09:44 -04:00
|
|
|
} else if (!fromServer && "AUDIO".equals(action)) {
|
|
|
|
// Use the alarm action that exchange (and blackberry) understand
|
|
|
|
// (exchange and blackberry don't understand audio actions)
|
|
|
|
vAlarm.setPropertyValue("ACTION", "DISPLAY");
|
|
|
|
}
|
2010-07-29 20:04:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-29 18:30:38 -04:00
|
|
|
/**
|
|
|
|
* Replace iCal4 (Snow Leopard) principal paths with mailto expression
|
|
|
|
*
|
|
|
|
* @param value attendee value or ics line
|
|
|
|
* @return fixed value
|
|
|
|
*/
|
|
|
|
protected String replaceIcal4Principal(String value) {
|
|
|
|
if (value.contains("/principals/__uuids__/")) {
|
|
|
|
return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-29 20:04:54 -04:00
|
|
|
private void fixAttendees(VObject vObject, boolean fromServer) {
|
2010-07-30 11:09:44 -04:00
|
|
|
if (vObject.properties != null) {
|
|
|
|
for (VProperty property : vObject.properties) {
|
|
|
|
if ("ATTENDEE".equalsIgnoreCase(property.getKey())) {
|
|
|
|
if (fromServer) {
|
|
|
|
// If this is coming from the server, strip out RSVP for this
|
|
|
|
// user as an attendee where the partstat is something other
|
|
|
|
// than PARTSTAT=NEEDS-ACTION since the RSVP confuses iCal4 into
|
|
|
|
// thinking the attendee has not replied
|
|
|
|
if (isCurrentUser(property) && property.hasParam("RSVP", "TRUE")) {
|
|
|
|
VProperty.Param partstat = property.getParam("PARTSTAT");
|
|
|
|
if (partstat == null || !"NEEDS-ACTION".equals(partstat.getValue())) {
|
|
|
|
property.removeParam("RSVP");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2010-07-29 20:04:54 -04:00
|
|
|
property.setValue(replaceIcal4Principal(property.getValue()));
|
2010-07-29 18:30:38 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-29 20:04:54 -04:00
|
|
|
}
|
2010-07-29 18:30:38 -04:00
|
|
|
}
|
2010-07-29 20:04:54 -04:00
|
|
|
|
2010-07-29 18:30:38 -04:00
|
|
|
}
|
|
|
|
|
2010-07-30 11:09:44 -04:00
|
|
|
private boolean isCurrentUser(VProperty property) {
|
|
|
|
return property.getValue().equalsIgnoreCase("mailto:" + email);
|
|
|
|
}
|
|
|
|
|
2010-08-17 09:39:14 -04:00
|
|
|
/**
|
|
|
|
* Return VTimezone object
|
2010-09-03 08:37:59 -04:00
|
|
|
*
|
2010-08-17 09:39:14 -04:00
|
|
|
* @return VTimezone
|
|
|
|
*/
|
|
|
|
public VObject getVTimezone() {
|
|
|
|
return vTimezone;
|
|
|
|
}
|
|
|
|
|
2010-07-29 17:18:59 -04:00
|
|
|
/**
|
|
|
|
* Convert X-CALENDARSERVER-ACCESS to CLASS.
|
2010-07-30 11:09:44 -04:00
|
|
|
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
|
2010-07-29 17:18:59 -04:00
|
|
|
*
|
2010-07-31 18:52:45 -04:00
|
|
|
* @param calendarServerAccess X-CALENDARSERVER-ACCESS value
|
2010-07-29 17:18:59 -04:00
|
|
|
* @return CLASS value
|
|
|
|
*/
|
|
|
|
protected String getEventClass(String calendarServerAccess) {
|
2010-07-29 18:30:38 -04:00
|
|
|
if ("PRIVATE".equalsIgnoreCase(calendarServerAccess)) {
|
2010-07-29 17:18:59 -04:00
|
|
|
return "CONFIDENTIAL";
|
2010-07-29 18:30:38 -04:00
|
|
|
} else if ("CONFIDENTIAL".equalsIgnoreCase(calendarServerAccess) || "RESTRICTED".equalsIgnoreCase(calendarServerAccess)) {
|
2010-07-29 17:18:59 -04:00
|
|
|
return "PRIVATE";
|
2010-09-14 17:56:20 -04:00
|
|
|
} else {
|
|
|
|
return null;
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert CLASS to X-CALENDARSERVER-ACCESS.
|
2010-07-30 11:09:44 -04:00
|
|
|
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt *
|
2010-07-31 18:52:45 -04:00
|
|
|
*
|
2010-07-29 17:18:59 -04:00
|
|
|
* @return X-CALENDARSERVER-ACCESS value
|
|
|
|
*/
|
|
|
|
protected String getCalendarServerAccess() {
|
2010-07-31 19:21:07 -04:00
|
|
|
String eventClass = getFirstVeventPropertyValue("CLASS");
|
2010-07-29 18:30:38 -04:00
|
|
|
if ("PRIVATE".equalsIgnoreCase(eventClass)) {
|
2010-07-29 17:18:59 -04:00
|
|
|
return "CONFIDENTIAL";
|
2010-07-29 18:30:38 -04:00
|
|
|
} else if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
|
2010-07-29 17:18:59 -04:00
|
|
|
return "PRIVATE";
|
2010-09-15 18:49:19 -04:00
|
|
|
} else {
|
2010-09-14 17:56:20 -04:00
|
|
|
return null;
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-03 08:37:59 -04:00
|
|
|
/**
|
|
|
|
* Get property value from first VEVENT in VCALENDAR.
|
|
|
|
*
|
|
|
|
* @param name property name
|
|
|
|
* @return property value
|
|
|
|
*/
|
2010-07-31 19:21:07 -04:00
|
|
|
public String getFirstVeventPropertyValue(String name) {
|
|
|
|
if (firstVevent == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return firstVevent.getPropertyValue(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected VProperty getFirstVeventProperty(String name) {
|
|
|
|
if (firstVevent == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return firstVevent.getProperty(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-20 19:08:42 -04:00
|
|
|
public List<VProperty> getFirstVeventProperties(String name) {
|
2010-07-31 19:21:07 -04:00
|
|
|
if (firstVevent == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return firstVevent.getProperties(name);
|
|
|
|
}
|
2010-07-31 18:52:45 -04:00
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Remove VAlarm from VCalendar.
|
|
|
|
*/
|
2010-09-15 18:49:19 -04:00
|
|
|
public void removeVAlarm() {
|
|
|
|
if (vObjects != null) {
|
2010-09-16 17:33:20 -04:00
|
|
|
for (VObject vObject : vObjects) {
|
2010-09-15 18:49:19 -04:00
|
|
|
if ("VEVENT".equals(vObject.type)) {
|
2011-06-08 15:58:29 -04:00
|
|
|
// As VALARM is the only possible inner object, just drop all objects
|
2010-09-15 18:49:19 -04:00
|
|
|
if (vObject.vObjects != null) {
|
|
|
|
vObject.vObjects = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Check if VCalendar has a VALARM item.
|
|
|
|
*
|
|
|
|
* @return true if VCalendar has a VALARM
|
|
|
|
*/
|
2010-09-15 18:49:19 -04:00
|
|
|
public boolean hasVAlarm() {
|
|
|
|
if (vObjects != null) {
|
2010-09-16 17:33:20 -04:00
|
|
|
for (VObject vObject : vObjects) {
|
2010-09-15 18:49:19 -04:00
|
|
|
if ("VEVENT".equals(vObject.type)) {
|
|
|
|
if (vObject.vObjects != null) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Check if this VCalendar is a meeting.
|
|
|
|
*
|
|
|
|
* @return true if this VCalendar has attendees
|
|
|
|
*/
|
2010-09-15 18:49:19 -04:00
|
|
|
public boolean isMeeting() {
|
|
|
|
return getFirstVeventProperty("ATTENDEE") != null;
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Check if current user is meeting organizer.
|
|
|
|
*
|
|
|
|
* @return true it user email matched organizer email
|
|
|
|
*/
|
2010-09-17 06:13:00 -04:00
|
|
|
public boolean isMeetingOrganizer() {
|
2011-06-08 15:58:29 -04:00
|
|
|
return email.equalsIgnoreCase(getEmailValue(getFirstVeventProperty("ORGANIZER")));
|
2010-09-17 06:13:00 -04:00
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Set property value on first VEVENT.
|
|
|
|
*
|
|
|
|
* @param propertyName property name
|
|
|
|
* @param propertyValue property value
|
|
|
|
*/
|
2010-09-16 05:27:06 -04:00
|
|
|
public void setFirstVeventPropertyValue(String propertyName, String propertyValue) {
|
|
|
|
firstVevent.setPropertyValue(propertyName, propertyValue);
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Add property on first VEVENT.
|
|
|
|
*
|
|
|
|
* @param vProperty property object
|
|
|
|
*/
|
2010-09-21 10:02:44 -04:00
|
|
|
public void addFirstVeventProperty(VProperty vProperty) {
|
|
|
|
firstVevent.addProperty(vProperty);
|
|
|
|
}
|
2010-09-15 18:49:19 -04:00
|
|
|
|
2011-10-24 18:24:38 -04:00
|
|
|
/**
|
|
|
|
* Check if this item is a VTODO item
|
|
|
|
* @return true with VTODO items
|
|
|
|
*/
|
2011-07-16 12:41:00 -04:00
|
|
|
public boolean isTodo() {
|
|
|
|
return "VTODO".equals(firstVevent.type);
|
|
|
|
}
|
|
|
|
|
2010-09-03 08:37:59 -04:00
|
|
|
/**
|
|
|
|
* VCalendar recipients for notifications
|
|
|
|
*/
|
|
|
|
public static class Recipients {
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* attendee list
|
|
|
|
*/
|
2010-09-21 09:12:19 -04:00
|
|
|
public String attendees;
|
2011-06-08 15:58:29 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* optional attendee list
|
|
|
|
*/
|
2010-09-21 09:12:19 -04:00
|
|
|
public String optionalAttendees;
|
2011-06-08 15:58:29 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* vCalendar organizer
|
|
|
|
*/
|
2010-09-21 09:12:19 -04:00
|
|
|
public String organizer;
|
2010-07-31 18:52:45 -04:00
|
|
|
}
|
|
|
|
|
2010-09-03 08:37:59 -04:00
|
|
|
/**
|
|
|
|
* Build recipients value for VCalendar.
|
|
|
|
*
|
|
|
|
* @param isNotification if true, filter recipients that should receive meeting notifications
|
|
|
|
* @return notification/event recipients
|
|
|
|
*/
|
2010-07-31 18:52:45 -04:00
|
|
|
public Recipients getRecipients(boolean isNotification) {
|
|
|
|
|
|
|
|
HashSet<String> attendees = new HashSet<String>();
|
|
|
|
HashSet<String> optionalAttendees = new HashSet<String>();
|
|
|
|
|
|
|
|
// get recipients from first VEVENT
|
2010-07-31 19:21:07 -04:00
|
|
|
List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE");
|
2010-07-31 18:52:45 -04:00
|
|
|
if (attendeeProperties != null) {
|
|
|
|
for (VProperty property : attendeeProperties) {
|
|
|
|
// exclude current user and invalid values from recipients
|
|
|
|
// also exclude no action attendees
|
|
|
|
String attendeeEmail = getEmailValue(property);
|
2010-08-01 19:07:53 -04:00
|
|
|
if (!email.equalsIgnoreCase(attendeeEmail) && attendeeEmail != null && attendeeEmail.indexOf('@') >= 0
|
2010-07-31 18:52:45 -04:00
|
|
|
// return all attendees for user calendar folder, filter for notifications
|
|
|
|
&& (!isNotification
|
|
|
|
// notify attendee if reply explicitly requested
|
|
|
|
|| (property.hasParam("RSVP", "TRUE"))
|
|
|
|
|| (
|
|
|
|
// workaround for iCal bug: do not notify if reply explicitly not requested
|
|
|
|
!(property.hasParam("RSVP", "FALSE")) &&
|
|
|
|
((property.hasParam("PARTSTAT", "NEEDS-ACTION")
|
|
|
|
// need to include other PARTSTATs participants for CANCEL notifications
|
|
|
|
|| property.hasParam("PARTSTAT", "ACCEPTED")
|
|
|
|
|| property.hasParam("PARTSTAT", "DECLINED")
|
|
|
|
|| property.hasParam("PARTSTAT", "TENTATIVE")))
|
|
|
|
))) {
|
|
|
|
if (property.hasParam("ROLE", "OPT-PARTICIPANT")) {
|
|
|
|
optionalAttendees.add(attendeeEmail);
|
|
|
|
} else {
|
|
|
|
attendees.add(attendeeEmail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Recipients recipients = new Recipients();
|
2010-07-31 19:21:07 -04:00
|
|
|
recipients.organizer = getEmailValue(getFirstVeventProperty("ORGANIZER"));
|
2010-07-31 18:52:45 -04:00
|
|
|
recipients.attendees = StringUtil.join(attendees, ", ");
|
|
|
|
recipients.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
|
|
|
|
return recipients;
|
|
|
|
}
|
2010-07-31 19:21:07 -04:00
|
|
|
|
2010-08-16 16:33:56 -04:00
|
|
|
protected String getAttendeeStatus() {
|
|
|
|
String status = null;
|
|
|
|
List<VProperty> attendeeProperties = getFirstVeventProperties("ATTENDEE");
|
|
|
|
if (attendeeProperties != null) {
|
|
|
|
for (VProperty property : attendeeProperties) {
|
|
|
|
String attendeeEmail = getEmailValue(property);
|
|
|
|
if (email.equalsIgnoreCase(attendeeEmail) && property.hasParam("PARTSTAT")) {
|
|
|
|
// found current user attendee line
|
|
|
|
status = property.getParam("PARTSTAT").getValue();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-06-08 15:58:29 -04:00
|
|
|
/**
|
|
|
|
* Get recurring VCalendar occurence exceptions.
|
|
|
|
*
|
|
|
|
* @return event occurences
|
|
|
|
*/
|
2011-04-12 16:32:22 -04:00
|
|
|
public List<VObject> getModifiedOccurrences() {
|
|
|
|
boolean first = true;
|
|
|
|
ArrayList<VObject> results = new ArrayList<VObject>();
|
2011-06-08 15:58:29 -04:00
|
|
|
for (VObject vObject : vObjects) {
|
|
|
|
if ("VEVENT".equals(vObject.type)) {
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
results.add(vObject);
|
|
|
|
}
|
|
|
|
}
|
2011-04-12 16:32:22 -04:00
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
2010-07-29 17:18:59 -04:00
|
|
|
}
|