mirror of
https://github.com/moparisthebest/davmail
synced 2024-12-14 03:32:22 -05:00
Caldav: major refactoring, move all ICS methods to Event inner class
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@863 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
ca1cb26fd5
commit
1ba600e818
@ -1634,6 +1634,13 @@ public class ExchangeSession {
|
||||
protected String href;
|
||||
protected String permanentUrl;
|
||||
protected String etag;
|
||||
protected String contentClass;
|
||||
protected String noneMatch;
|
||||
/**
|
||||
* ICS content
|
||||
*/
|
||||
protected String icsBody;
|
||||
|
||||
|
||||
protected MimePart getCalendarMimePart(MimeMultipart multiPart) throws IOException, MessagingException {
|
||||
MimePart bodyPart = null;
|
||||
@ -1721,210 +1728,35 @@ public class ExchangeSession {
|
||||
public String getEtag() {
|
||||
return etag;
|
||||
}
|
||||
|
||||
protected String fixTimezoneId(String line, String validTimezoneId) {
|
||||
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search calendar messages in provided folder.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @return list of calendar messages as Event objects
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Event> getEventMessages(String folderPath) throws IOException {
|
||||
List<Event> result;
|
||||
try {
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
|
||||
" AND (NOT \"" + scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName() + "\" = 'CALDAV:schedule-processed')\n" +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
||||
result = getEvents(folderPath, searchQuery);
|
||||
} catch (HttpException e) {
|
||||
// failover to DAV:comment property on some Exchange servers
|
||||
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
|
||||
scheduleStateProperty = DavPropertyName.create("comment", Namespace.getNamespace("DAV:"));
|
||||
result = getEventMessages(folderPath);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
protected void splitExDate(ICSBufferedWriter result, String line) {
|
||||
int cur = line.lastIndexOf(':') + 1;
|
||||
String start = line.substring(0, cur);
|
||||
|
||||
for (int next = line.indexOf(',', cur); next != -1; next = line.indexOf(',', cur)) {
|
||||
String val = line.substring(cur, next);
|
||||
result.writeLine(start + val);
|
||||
|
||||
cur = next + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search calendar events in provided folder.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @return list of calendar events
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Event> getAllEvents(String folderPath) throws IOException {
|
||||
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay", Integer.MAX_VALUE);
|
||||
String dateCondition = "";
|
||||
if (caldavPastDelay != Integer.MAX_VALUE) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
|
||||
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
|
||||
result.writeLine(start + line.substring(cur));
|
||||
}
|
||||
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE (" +
|
||||
// VTODO events have a null instancetype
|
||||
" \"urn:schemas:calendar:instancetype\" is null OR" +
|
||||
" \"urn:schemas:calendar:instancetype\" = 1\n" +
|
||||
" OR (\"urn:schemas:calendar:instancetype\" = 0\n" +
|
||||
dateCondition +
|
||||
" )) AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
||||
return getEvents(folderPath, searchQuery);
|
||||
protected String getAllDayLine(String line) throws IOException {
|
||||
int keyIndex = line.indexOf(';');
|
||||
int valueIndex = line.lastIndexOf(':');
|
||||
int valueEndIndex = line.lastIndexOf('T');
|
||||
if (valueIndex < 0 || valueEndIndex < 0) {
|
||||
throw new DavMailException("EXCEPTION_INVALID_ICS_LINE", line);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search calendar events or messages in provided folder matching the search query.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param searchQuery Exchange search query
|
||||
* @return list of calendar messages as Event objects
|
||||
* @throws IOException on error
|
||||
*/
|
||||
protected List<Event> getEvents(String folderPath, String searchQuery) throws IOException {
|
||||
List<Event> events = new ArrayList<Event>();
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
|
||||
for (MultiStatusResponse response : responses) {
|
||||
events.add(buildEvent(response));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event named eventName in folder
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Event getEvent(String folderPath, String eventName) throws IOException {
|
||||
String eventPath = folderPath + '/' + eventName;
|
||||
return getEvent(eventPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event by url
|
||||
*
|
||||
* @param eventPath Event path
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Event getEvent(String eventPath) throws IOException {
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(eventPath), 0, EVENT_REQUEST_PROPERTIES);
|
||||
if (responses.length == 0) {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
return buildEvent(responses[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete event named eventName in folder
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @return HTTP status
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public int deleteEvent(String folderPath, String eventName) throws IOException {
|
||||
String eventPath = URIUtil.encodePath(folderPath + '/' + eventName);
|
||||
int status;
|
||||
if (inboxUrl.endsWith(folderPath)) {
|
||||
// do not delete calendar messages, mark read and processed
|
||||
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
||||
list.add(new DefaultDavProperty(scheduleStateProperty, "CALDAV:schedule-processed"));
|
||||
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), "1"));
|
||||
PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
|
||||
DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
|
||||
status = HttpStatus.SC_OK;
|
||||
} else {
|
||||
status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
protected Event buildEvent(MultiStatusResponse calendarResponse) throws URIException {
|
||||
Event event = new Event();
|
||||
event.href = URIUtil.decode(calendarResponse.getHref());
|
||||
event.permanentUrl = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
|
||||
event.etag = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "getetag", Namespace.getNamespace("DAV:"));
|
||||
return event;
|
||||
}
|
||||
|
||||
private static int dumpIndex;
|
||||
private String defaultSound = "Basso";
|
||||
|
||||
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
|
||||
String logFileDirectory = Settings.getLogFileDirectory();
|
||||
|
||||
// additional setting to activate ICS dump (not available in GUI)
|
||||
int dumpMax = Settings.getIntProperty("davmail.dumpICS");
|
||||
if (dumpMax > 0) {
|
||||
if (dumpIndex > dumpMax) {
|
||||
// Delete the oldest dump file
|
||||
final int oldest = dumpIndex - dumpMax;
|
||||
try {
|
||||
File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
if (name.endsWith(".ics")) {
|
||||
int dashIndex = name.indexOf('-');
|
||||
if (dashIndex > 0) {
|
||||
try {
|
||||
int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
|
||||
return fileIndex < oldest;
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
for (File file : oldestFiles) {
|
||||
if (!file.delete()) {
|
||||
LOGGER.warn("Unable to delete " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder filePath = new StringBuilder();
|
||||
filePath.append(logFileDirectory).append('/')
|
||||
.append(dumpIndex)
|
||||
.append(after ? "-to" : "-from")
|
||||
.append((after ^ fromServer) ? "-server" : "-client")
|
||||
.append(".ics");
|
||||
if ((icsBody != null) && (icsBody.length() > 0)) {
|
||||
FileWriter fileWriter = null;
|
||||
try {
|
||||
fileWriter = new FileWriter(filePath.toString());
|
||||
fileWriter.write(icsBody);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
} finally {
|
||||
if (fileWriter != null) {
|
||||
try {
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
|
||||
String key = line.substring(0, Math.max(keyIndex, valueIndex));
|
||||
return key + ";VALUE=DATE:" + dateValue;
|
||||
}
|
||||
|
||||
protected String fixICS(String icsBody, boolean fromServer) throws IOException {
|
||||
@ -2170,110 +2002,69 @@ public class ExchangeSession {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
|
||||
String logFileDirectory = Settings.getLogFileDirectory();
|
||||
|
||||
// additional setting to activate ICS dump (not available in GUI)
|
||||
int dumpMax = Settings.getIntProperty("davmail.dumpICS");
|
||||
if (dumpMax > 0) {
|
||||
if (dumpIndex > dumpMax) {
|
||||
// Delete the oldest dump file
|
||||
final int oldest = dumpIndex - dumpMax;
|
||||
try {
|
||||
File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
if (name.endsWith(".ics")) {
|
||||
int dashIndex = name.indexOf('-');
|
||||
if (dashIndex > 0) {
|
||||
try {
|
||||
int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
|
||||
return fileIndex < oldest;
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
for (File file : oldestFiles) {
|
||||
if (!file.delete()) {
|
||||
LOGGER.warn("Unable to delete " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected String fixTimezoneId(String line, String validTimezoneId) {
|
||||
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId);
|
||||
StringBuilder filePath = new StringBuilder();
|
||||
filePath.append(logFileDirectory).append('/')
|
||||
.append(dumpIndex)
|
||||
.append(after ? "-to" : "-from")
|
||||
.append((after ^ fromServer) ? "-server" : "-client")
|
||||
.append(".ics");
|
||||
if ((icsBody != null) && (icsBody.length() > 0)) {
|
||||
FileWriter fileWriter = null;
|
||||
try {
|
||||
fileWriter = new FileWriter(filePath.toString());
|
||||
fileWriter.write(icsBody);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
} finally {
|
||||
if (fileWriter != null) {
|
||||
try {
|
||||
fileWriter.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
|
||||
protected void splitExDate(ICSBufferedWriter result, String line) {
|
||||
int cur = line.lastIndexOf(':') + 1;
|
||||
String start = line.substring(0, cur);
|
||||
|
||||
for (int next = line.indexOf(',', cur); next != -1; next = line.indexOf(',', cur)) {
|
||||
String val = line.substring(cur, next);
|
||||
result.writeLine(start + val);
|
||||
|
||||
cur = next + 1;
|
||||
}
|
||||
|
||||
result.writeLine(start + line.substring(cur));
|
||||
}
|
||||
|
||||
protected String getAllDayLine(String line) throws IOException {
|
||||
int keyIndex = line.indexOf(';');
|
||||
int valueIndex = line.lastIndexOf(':');
|
||||
int valueEndIndex = line.lastIndexOf('T');
|
||||
if (valueIndex < 0 || valueEndIndex < 0) {
|
||||
throw new DavMailException("EXCEPTION_INVALID_ICS_LINE", line);
|
||||
}
|
||||
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
|
||||
String key = line.substring(0, Math.max(keyIndex, valueIndex));
|
||||
return key + ";VALUE=DATE:" + dateValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Event result object to hold HTTP status and event etag from an event creation/update.
|
||||
*/
|
||||
public static class EventResult {
|
||||
/**
|
||||
* HTTP status
|
||||
*/
|
||||
public int status;
|
||||
/**
|
||||
* Event etag from response HTTP header
|
||||
*/
|
||||
public String etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send the MIME message for the provided ICS event.
|
||||
*
|
||||
* @param icsBody event in iCalendar format
|
||||
* @return HTTP status
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public int sendEvent(String icsBody) throws IOException {
|
||||
String messageUrl = draftsUrl + '/' + UUID.randomUUID().toString() + ".EML";
|
||||
int status = internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:calendarmessage", icsBody, null, null).status;
|
||||
if (status != HttpStatus.SC_CREATED) {
|
||||
return status;
|
||||
} else {
|
||||
MoveMethod method = new MoveMethod(URIUtil.encodePath(messageUrl), URIUtil.encodePath(sendmsgUrl), true);
|
||||
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
||||
if (status != HttpStatus.SC_OK) {
|
||||
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update event on the Exchange server
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @param icsBody event body in iCalendar format
|
||||
* @param etag previous event etag to detect concurrent updates
|
||||
* @param noneMatch if-none-match header value
|
||||
* @return HTTP response event result (status and etag)
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public EventResult createOrUpdateEvent(String folderPath, String eventName, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
String messageUrl = folderPath + '/' + eventName;
|
||||
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", icsBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
protected String getICSMethod(String icsBody) {
|
||||
String icsMethod = StringUtil.getToken(icsBody, "METHOD:", "\r");
|
||||
if (icsMethod == null) {
|
||||
// default method is REQUEST
|
||||
icsMethod = "REQUEST";
|
||||
}
|
||||
return icsMethod;
|
||||
}
|
||||
|
||||
protected String getICSValue(String icsBody, String prefix, String defval) throws IOException {
|
||||
@ -2311,7 +2102,7 @@ public class ExchangeSession {
|
||||
return getICSValue(icsBody, "DESCRIPTION:", "");
|
||||
}
|
||||
|
||||
static class Participants {
|
||||
class Participants {
|
||||
String attendees;
|
||||
String organizer;
|
||||
}
|
||||
@ -2320,12 +2111,11 @@ public class ExchangeSession {
|
||||
* Parse ics event for attendees and organizer.
|
||||
* For notifications, only include attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION
|
||||
*
|
||||
* @param icsBody ics event
|
||||
* @param isNotification get only notified attendees
|
||||
* @return participants
|
||||
* @throws IOException on error
|
||||
*/
|
||||
protected Participants getParticipants(String icsBody, boolean isNotification) throws IOException {
|
||||
protected Participants getParticipants(boolean isNotification) throws IOException {
|
||||
HashSet<String> attendees = new HashSet<String>();
|
||||
String organizer = null;
|
||||
BufferedReader reader = null;
|
||||
@ -2380,12 +2170,21 @@ public class ExchangeSession {
|
||||
return participants;
|
||||
}
|
||||
|
||||
protected EventResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
String uid = UUID.randomUUID().toString();
|
||||
protected String getICSMethod(String icsBody) {
|
||||
String icsMethod = StringUtil.getToken(icsBody, "METHOD:", "\r");
|
||||
if (icsMethod == null) {
|
||||
// default method is REQUEST
|
||||
icsMethod = "REQUEST";
|
||||
}
|
||||
return icsMethod;
|
||||
}
|
||||
|
||||
protected EventResult createOrUpdate() throws IOException {
|
||||
String boundary = UUID.randomUUID().toString();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStreamWriter writer = new OutputStreamWriter(baos, "ASCII");
|
||||
int status = 0;
|
||||
PutMethod putmethod = new PutMethod(URIUtil.encodePath(messageUrl));
|
||||
PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
|
||||
putmethod.setRequestHeader("Translate", "f");
|
||||
putmethod.setRequestHeader("Overwrite", "f");
|
||||
if (etag != null) {
|
||||
@ -2408,7 +2207,7 @@ public class ExchangeSession {
|
||||
writer.write("\r\n");
|
||||
if ("urn:content-classes:calendarmessage".equals(contentClass)) {
|
||||
// need to parse attendees and organizer to build recipients
|
||||
Participants participants = getParticipants(icsBody, true);
|
||||
Participants participants = getParticipants(true);
|
||||
String recipients;
|
||||
if (email.equalsIgnoreCase(participants.organizer)) {
|
||||
// current user is organizer => notify all
|
||||
@ -2433,7 +2232,7 @@ public class ExchangeSession {
|
||||
writer.write("Subject: " + MimeUtility.encodeText(getICSSummary(icsBody), "UTF-8", null) + "\r\n");
|
||||
|
||||
// need to parse attendees and organizer to build recipients
|
||||
Participants participants = getParticipants(icsBody, false);
|
||||
Participants participants = getParticipants(false);
|
||||
// storing appointment, full recipients header
|
||||
writer.write("To: ");
|
||||
if (participants.attendees != null) {
|
||||
@ -2464,13 +2263,13 @@ public class ExchangeSession {
|
||||
writer.write("MIME-Version: 1.0\r\n" +
|
||||
"Content-Type: multipart/alternative;\r\n" +
|
||||
"\tboundary=\"----=_NextPart_");
|
||||
writer.write(uid);
|
||||
writer.write(boundary);
|
||||
writer.write("\"\r\n" +
|
||||
"\r\n" +
|
||||
"This is a multi-part message in MIME format.\r\n" +
|
||||
"\r\n" +
|
||||
"------=_NextPart_");
|
||||
writer.write(uid);
|
||||
writer.write(boundary);
|
||||
|
||||
// Write a part of the message that contains the
|
||||
// ICS description so that invites contain the description text
|
||||
@ -2486,7 +2285,7 @@ public class ExchangeSession {
|
||||
baos.write(description.getBytes("UTF-8"));
|
||||
writer.write("\r\n" +
|
||||
"------=_NextPart_" +
|
||||
uid);
|
||||
boundary);
|
||||
|
||||
}
|
||||
|
||||
@ -2503,7 +2302,7 @@ public class ExchangeSession {
|
||||
writer.flush();
|
||||
baos.write(fixICS(icsBody, false).getBytes("UTF-8"));
|
||||
writer.write("\r\n------=_NextPart_");
|
||||
writer.write(uid);
|
||||
writer.write(boundary);
|
||||
writer.write("--\r\n");
|
||||
writer.close();
|
||||
putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), "message/rfc822"));
|
||||
@ -2512,9 +2311,9 @@ public class ExchangeSession {
|
||||
status = httpClient.executeMethod(putmethod);
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
if (etag != null) {
|
||||
LOGGER.debug("Updated event " + messageUrl);
|
||||
LOGGER.debug("Updated event " + href);
|
||||
} else {
|
||||
LOGGER.warn("Overwritten event " + messageUrl);
|
||||
LOGGER.warn("Overwritten event " + href);
|
||||
}
|
||||
} else if (status != HttpURLConnection.HTTP_CREATED) {
|
||||
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
|
||||
@ -2538,13 +2337,230 @@ public class ExchangeSession {
|
||||
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
|
||||
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
|
||||
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:")), contentClass));
|
||||
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(messageUrl), propertyList);
|
||||
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
|
||||
int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
|
||||
if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
|
||||
LOGGER.warn("Unable to patch event to trigger activeSync push");
|
||||
}
|
||||
}
|
||||
return eventResult;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search calendar messages in provided folder.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @return list of calendar messages as Event objects
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Event> getEventMessages(String folderPath) throws IOException {
|
||||
List<Event> result;
|
||||
try {
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
|
||||
" AND (NOT \"" + scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName() + "\" = 'CALDAV:schedule-processed')\n" +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
||||
result = getEvents(folderPath, searchQuery);
|
||||
} catch (HttpException e) {
|
||||
// failover to DAV:comment property on some Exchange servers
|
||||
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
|
||||
scheduleStateProperty = DavPropertyName.create("comment", Namespace.getNamespace("DAV:"));
|
||||
result = getEventMessages(folderPath);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search calendar events in provided folder.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @return list of calendar events
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Event> getAllEvents(String folderPath) throws IOException {
|
||||
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay", Integer.MAX_VALUE);
|
||||
String dateCondition = "";
|
||||
if (caldavPastDelay != Integer.MAX_VALUE) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
|
||||
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
|
||||
}
|
||||
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE (" +
|
||||
// VTODO events have a null instancetype
|
||||
" \"urn:schemas:calendar:instancetype\" is null OR" +
|
||||
" \"urn:schemas:calendar:instancetype\" = 1\n" +
|
||||
" OR (\"urn:schemas:calendar:instancetype\" = 0\n" +
|
||||
dateCondition +
|
||||
" )) AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
||||
return getEvents(folderPath, searchQuery);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search calendar events or messages in provided folder matching the search query.
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param searchQuery Exchange search query
|
||||
* @return list of calendar messages as Event objects
|
||||
* @throws IOException on error
|
||||
*/
|
||||
protected List<Event> getEvents(String folderPath, String searchQuery) throws IOException {
|
||||
List<Event> events = new ArrayList<Event>();
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
|
||||
for (MultiStatusResponse response : responses) {
|
||||
events.add(buildEvent(response));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event named eventName in folder
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Event getEvent(String folderPath, String eventName) throws IOException {
|
||||
String eventPath = folderPath + '/' + eventName;
|
||||
return getEvent(eventPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event by url
|
||||
*
|
||||
* @param eventPath Event path
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Event getEvent(String eventPath) throws IOException {
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(eventPath), 0, EVENT_REQUEST_PROPERTIES);
|
||||
if (responses.length == 0) {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
return buildEvent(responses[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete event named eventName in folder
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @return HTTP status
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public int deleteEvent(String folderPath, String eventName) throws IOException {
|
||||
String eventPath = URIUtil.encodePath(folderPath + '/' + eventName);
|
||||
int status;
|
||||
if (inboxUrl.endsWith(folderPath)) {
|
||||
// do not delete calendar messages, mark read and processed
|
||||
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
||||
list.add(new DefaultDavProperty(scheduleStateProperty, "CALDAV:schedule-processed"));
|
||||
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), "1"));
|
||||
PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
|
||||
DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
|
||||
status = HttpStatus.SC_OK;
|
||||
} else {
|
||||
status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
protected Event buildEvent(MultiStatusResponse calendarResponse) throws URIException {
|
||||
Event event = new Event();
|
||||
event.href = URIUtil.decode(calendarResponse.getHref());
|
||||
event.permanentUrl = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
|
||||
event.etag = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "getetag", Namespace.getNamespace("DAV:"));
|
||||
return event;
|
||||
}
|
||||
|
||||
private static int dumpIndex;
|
||||
private String defaultSound = "Basso";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event result object to hold HTTP status and event etag from an event creation/update.
|
||||
*/
|
||||
public static class EventResult {
|
||||
/**
|
||||
* HTTP status
|
||||
*/
|
||||
public int status;
|
||||
/**
|
||||
* Event etag from response HTTP header
|
||||
*/
|
||||
public String etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send the MIME message for the provided ICS event.
|
||||
*
|
||||
* @param icsBody event in iCalendar format
|
||||
* @return HTTP status
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public int sendEvent(String icsBody) throws IOException {
|
||||
String messageUrl = draftsUrl + '/' + UUID.randomUUID().toString() + ".EML";
|
||||
int status = internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:calendarmessage", icsBody, null, null).status;
|
||||
if (status != HttpStatus.SC_CREATED) {
|
||||
return status;
|
||||
} else {
|
||||
MoveMethod method = new MoveMethod(URIUtil.encodePath(messageUrl), URIUtil.encodePath(sendmsgUrl), true);
|
||||
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
||||
if (status != HttpStatus.SC_OK) {
|
||||
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update event on the Exchange server
|
||||
*
|
||||
* @param folderPath Exchange folder path
|
||||
* @param eventName event name
|
||||
* @param icsBody event body in iCalendar format
|
||||
* @param etag previous event etag to detect concurrent updates
|
||||
* @param noneMatch if-none-match header value
|
||||
* @return HTTP response event result (status and etag)
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public EventResult createOrUpdateEvent(String folderPath, String eventName, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
String messageUrl = folderPath + '/' + eventName;
|
||||
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", icsBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
protected EventResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
Event event = new Event();
|
||||
event.contentClass = contentClass;
|
||||
event.icsBody = icsBody;
|
||||
event.href = messageUrl;
|
||||
event.etag = etag;
|
||||
event.noneMatch = noneMatch;
|
||||
return event.createOrUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user