diff --git a/src/java/davmail/caldav/CaldavConnection.java b/src/java/davmail/caldav/CaldavConnection.java index ab0d24e5..646611f1 100644 --- a/src/java/davmail/caldav/CaldavConnection.java +++ b/src/java/davmail/caldav/CaldavConnection.java @@ -704,7 +704,13 @@ public class CaldavConnection extends AbstractConnection { appendEventsResponses(response, request, events); } else { // TODO: handle contacts ? - events = session.searchEvents(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd); + if (request.vTodoOnly) { + events = session.searchTasksOnly(request.getFolderPath()); + } else if (request.vEventOnly) { + events = session.searchEventsOnly(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd); + } else { + events = session.searchEvents(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd); + } appendEventsResponses(response, request, events); } @@ -1209,6 +1215,8 @@ public class CaldavConnection extends AbstractConnection { protected boolean isMultiGet; protected String timeRangeStart; protected String timeRangeEnd; + protected boolean vTodoOnly; + protected boolean vEventOnly; protected CaldavRequest(String command, String path, Map headers, String body) throws IOException { this.command = command; @@ -1418,7 +1426,14 @@ public class CaldavConnection extends AbstractConnection { public void handleCompFilter(XMLStreamReader reader) throws XMLStreamException { while (reader.hasNext() && !isEndTag(reader, "comp-filter")) { reader.nextTag(); - if (XMLStreamUtil.isStartTag(reader, "time-range")) { + if (XMLStreamUtil.isStartTag(reader, "comp-filter")) { + String name = reader.getAttributeValue(null, "name"); + if ("VEVENT".equals(name)) { + vEventOnly = true; + } else if ("VTODO".equals(name)) { + vTodoOnly = true; + } + } else if (XMLStreamUtil.isStartTag(reader, "time-range")) { timeRangeStart = reader.getAttributeValue(null, "start"); timeRangeEnd = reader.getAttributeValue(null, "end"); } diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 4b27ae59..2055bd01 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -2089,9 +2089,9 @@ public abstract class ExchangeSession { writer.writeHeader("Date", new Date()); // Make sure invites have a proper subject line - String subject = vCalendar.getFirstVeventPropertyValue("SUMMARY"); - if (subject == null) { - subject = BundleMessage.format("MEETING_REQUEST"); + String vEventSubject = vCalendar.getFirstVeventPropertyValue("SUMMARY"); + if (vEventSubject == null) { + vEventSubject = BundleMessage.format("MEETING_REQUEST"); } // Write a part of the message that contains the @@ -2267,7 +2267,25 @@ public abstract class ExchangeSession { dateCondition = gt("dtstart", formatSearchDate(cal.getTime())); } - return searchEvents(folderPath, dateCondition); + return searchEvents(folderPath, or(isNull("instancetype"), + isEqualTo("instancetype", 1), + and(isEqualTo("instancetype", 0), dateCondition))); + } + + protected Condition getRangeCondition(String timeRangeStart, String timeRangeEnd) throws IOException { + try { + SimpleDateFormat parser = getZuluDateFormat(); + ExchangeSession.MultiCondition andCondition = and(); + if (timeRangeStart != null) { + andCondition.add(gte("dtstart", formatSearchDate(parser.parse(timeRangeStart)))); + } + if (timeRangeEnd != null) { + andCondition.add(lte("dtend", formatSearchDate(parser.parse(timeRangeEnd)))); + } + return andCondition; + } catch (ParseException e) { + throw new IOException(e); + } } /** @@ -2280,30 +2298,47 @@ public abstract class ExchangeSession { * @throws IOException on error */ public List searchEvents(String folderPath, String timeRangeStart, String timeRangeEnd) throws IOException { - try { - SimpleDateFormat parser = getZuluDateFormat(); - ExchangeSession.MultiCondition andCondition = and(); - if (timeRangeStart != null) { - andCondition.add(gte("dtstart", formatSearchDate(parser.parse(timeRangeStart)))); - } - if (timeRangeEnd != null) { - andCondition.add(lte("dtend", formatSearchDate(parser.parse(timeRangeEnd)))); - } - return searchEvents(folderPath, andCondition); - } catch (ParseException e) { - throw new IOException(e); - } + Condition dateCondition = getRangeCondition(timeRangeStart, timeRangeEnd); + return searchEvents(folderPath, or(isNull("instancetype"), + isEqualTo("instancetype", 1), + and(isEqualTo("instancetype", 0), dateCondition))); + } + + /** + * Search events between start and end, exclude tasks. + * + * @param folderPath Exchange folder path + * @param timeRangeStart date range start in zulu format + * @param timeRangeEnd date range start in zulu format + * @return list of calendar events + * @throws IOException on error + */ + public List searchEventsOnly(String folderPath, String timeRangeStart, String timeRangeEnd) throws IOException { + Condition dateCondition = getRangeCondition(timeRangeStart, timeRangeEnd); + return searchEvents(folderPath, or(isEqualTo("instancetype", 1), + and(isEqualTo("instancetype", 0), dateCondition))); + } + + /** + * Search tasks only (VTODO). + * + * @param folderPath Exchange folder path + * @return list of calendar events + * @throws IOException on error + */ + public List searchTasksOnly(String folderPath) throws IOException { + return searchEvents(folderPath, isNull("instancetype")); } /** * Search calendar events in provided folder. * - * @param folderPath Exchange folder path - * @param dateCondition date filter + * @param folderPath Exchange folder path + * @param filter search filter * @return list of calendar events * @throws IOException on error */ - public List searchEvents(String folderPath, Condition dateCondition) throws IOException { + public List searchEvents(String folderPath, Condition filter) throws IOException { Condition privateCondition = null; if (isSharedFolder(folderPath)) { @@ -2312,10 +2347,7 @@ public abstract class ExchangeSession { } // instancetype 0 single appointment / 1 master recurring appointment return searchEvents(folderPath, ITEM_PROPERTIES, - and(or(isNull("instancetype"), - isEqualTo("instancetype", 1), - and(isEqualTo("instancetype", 0), dateCondition)), - privateCondition)); + and(filter, privateCondition)); } /** diff --git a/src/test/davmail/caldav/TestCaldav.java b/src/test/davmail/caldav/TestCaldav.java index 3297db83..cf3d4075 100644 --- a/src/test/davmail/caldav/TestCaldav.java +++ b/src/test/davmail/caldav/TestCaldav.java @@ -33,12 +33,16 @@ import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +import org.apache.log4j.Level; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.List; /** * Test Caldav listener. @@ -121,6 +125,13 @@ public class TestCaldav extends AbstractDavMailTestCase { assertEquals(HttpStatus.SC_OK, method.getStatusCode()); } + public void testGetOtherUserCalendar() throws IOException { + Settings.setLoggingLevel("httpclient.wire", Level.DEBUG); + PropFindMethod method = new PropFindMethod("/principals/users/" + Settings.getProperty("davmail.to") + "/calendar/"); + httpClient.executeMethod(method); + assertEquals(HttpStatus.SC_OK, method.getStatusCode()); + } + public void testReportCalendar() throws IOException, DavException { SimpleDateFormat formatter = ExchangeSession.getZuluDateFormat(); Calendar cal = Calendar.getInstance(); @@ -148,17 +159,60 @@ public class TestCaldav extends AbstractDavMailTestCase { assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode()); MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multiStatus.getResponses(); - + ExchangeSession.Condition dateCondition = session.and( + session.gt("dtstart", session.formatSearchDate(start)), + session.lt("dtend", session.formatSearchDate(end)) + ); List events = session.searchEvents("/users/" + session.getEmail() + "/calendar/", - session.and( - session.gt("dtstart", session.formatSearchDate(start)), - session.lt("dtend", session.formatSearchDate(end)) - ) + session.or(session.isNull("instancetype"), + session.isEqualTo("instancetype", 1), + session.and(session.isEqualTo("instancetype", 0), dateCondition)) + ); assertEquals(events.size(), responses.length); } + public void testReportTasks() throws IOException, DavException { + StringBuilder buffer = new StringBuilder(); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/", buffer.toString()); + httpClient.executeMethod(method); + assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode()); + MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(); + MultiStatusResponse[] responses = multiStatus.getResponses(); + } + + public void testReportEventsOnly() throws IOException, DavException { + StringBuilder buffer = new StringBuilder(); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append(""); + SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/", buffer.toString()); + httpClient.executeMethod(method); + assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode()); + MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(); + MultiStatusResponse[] responses = multiStatus.getResponses(); + } + public void testCreateCalendar() throws IOException { String folderName = "test & accentué"; String encodedFolderpath = URIUtil.encodePath("/users/" + session.getEmail() + "/calendar/" + folderName + '/');