package com.renaghan.notes2cloud; import org.apache.http.HttpResponse; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.FileWriter; import java.io.StringReader; import java.text.SimpleDateFormat; import java.util.*; /** * Download entries from cloud * * @author prenagha */ public class CloudDownload { private final SimpleDateFormat utc = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); private final SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); private final Utils utils = Notes2Cloud.getUtils(); private final Set<Event> events = new LinkedHashSet<Event>(200); private final Date minDate; private final Date maxDate; public CloudDownload(Date minDate, Date maxDate) { utc.setTimeZone(TimeZone.getTimeZone("UTC")); this.minDate = minDate; this.maxDate = maxDate; events(); } public Set<Event> getEvents() { return events; } private void events() { String calUrl = utils.getProperty("cloud.baseUrl") + utils.getProperty("cloud.calendarUrl"); try { HttpCalDavReport calendar = new HttpCalDavReport(calUrl); String req = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + " <C:calendar-query xmlns:D=\"DAV:\"\n" + " xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n" + " <D:prop>\n" + " <C:calendar-data>\n" + " <C:comp name=\"VCALENDAR\">\n" + " <C:comp name=\"VEVENT\">\n" + " <C:prop name=\"SUMMARY\"/>\n" + " <C:prop name=\"UID\"/>\n" + " <C:prop name=\"DTSTART\"/>\n" + " <C:prop name=\"DTEND\"/>\n" + " <C:prop name=\"LOCATION\"/>\n" + " <C:prop name=\"DESCRIPTION\"/>\n" + " </C:comp>\n" + " <C:comp name=\"VTIMEZONE\"/>\n" + " </C:comp>\n" + " </C:calendar-data>\n" + " </D:prop>\n" + " <C:filter>\n" + " <C:comp-filter name=\"VCALENDAR\">\n" + " <C:comp-filter name=\"VEVENT\">\n" + " <C:time-range start=\"" + utc.format(minDate) + "\"\n" + " end=\"" + utc.format(maxDate) + "\"/>\n" + " </C:comp-filter>\n" + " </C:comp-filter>\n" + " </C:filter>\n" + " </C:calendar-query>"; calendar.setEntity(new StringEntity(req)); HttpResponse response = utils.getHttpClient().execute(calendar); String xml = response.getEntity() == null ? "" : EntityUtils.toString(response.getEntity()); if ("true".equals(Notes2Cloud.getUtils().getProperty("debug"))) { FileWriter fw = new FileWriter("cloud-calendar-response.xml"); fw.write(xml); fw.close(); } parse(xml); } catch (Exception e) { throw new RuntimeException("Error getting cloud calendar " + calUrl, e); } } private void parse(String xml) { String item = ""; try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(new InputSource(new StringReader(xml))); NodeList responses = doc.getElementsByTagName("response"); for (int r = 0; r < responses.getLength(); r++) { Element response = (Element) responses.item(r); Element hrefTag = (Element) response.getElementsByTagName("href").item(0); String href = hrefTag.getTextContent(); Element calendardata = (Element) response.getElementsByTagName("calendar-data").item(0); item = calendardata.getTextContent(); String uniqueId = null; String name = null; Date start = null; Date end = null; String location = null; boolean allDay = false; Map<String, String> lines = parseLines(item); for (Map.Entry<String, String> entry : lines.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if ("SUMMARY".equals(key)) { name = unescape(value); } else if ("UID".equals(key)) { uniqueId = value; } else if ("DTSTART".equals(key)) { allDay = value.startsWith("VALUE=DATE:"); start = toDate(value); } else if ("DTEND".equals(key)) { end = toDate(value); } else if ("LOCATION".equals(key)) { location = unescape(value); } } if (uniqueId != null && name != null && start != null && end != null && !start.after(maxDate) && !start.before(minDate)) { events.add(new Event(href, uniqueId, name, location, start, end, allDay)); } } } catch (Exception e) { throw new RuntimeException("Error parsing cloud calendar xml -- " + item, e); } } private Map<String, String> parseLines(String element) { LinkedHashMap<String, String> lines = new LinkedHashMap<String, String>(); StringTokenizer tokenizer = new StringTokenizer(element, "\n", false); String lastKey = ""; while (tokenizer.hasMoreTokens()) { String line = tokenizer.nextToken(); if (line.startsWith(" ")) { lines.put(lastKey, lines.get(lastKey) + line.substring(1)); } int delim = line.indexOf(":"); int semi = line.indexOf(";"); if (semi > 0 && semi < delim) delim = semi; if (delim > 0) { lastKey = line.substring(0, delim).trim(); lines.put(lastKey, line.substring(delim + 1)); } } return lines; } private Date toDate(String dt) throws Exception { if (dt == null) return null; String year, month, day, hour, minute, second; TimeZone tz = TimeZone.getDefault(); if (dt.startsWith("TZID=")) { // TZID=America/New_York:20111110T150000 int eq = dt.indexOf("="); int colon = dt.indexOf(":"); String timezone = dt.substring(eq + 1, colon); tz = TimeZone.getTimeZone(timezone); int st = colon + 1; year = dt.substring(st, st + 4); month = dt.substring(st + 4, st + 6); day = dt.substring(st + 6, st + 8); hour = dt.substring(st + 9, st + 11); minute = dt.substring(st + 11, st + 13); second = dt.substring(st + 13, st + 15); } else if (dt.startsWith("VALUE=DATE:")) { // DTSTART;VALUE=DATE:20111110 year = dt.substring(11, 15); month = dt.substring(15, 17); day = dt.substring(17, 19); hour = "00"; minute = "00"; second = "00"; } else if (dt.endsWith("Z")) { tz = TimeZone.getTimeZone("UTC"); int st = 0; year = dt.substring(st, st + 4); month = dt.substring(st + 4, st + 6); day = dt.substring(st + 6, st + 8); hour = dt.substring(st + 9, st + 11); minute = dt.substring(st + 11, st + 13); second = dt.substring(st + 13, st + 15); } else { throw new IllegalArgumentException("Invalid cloud date " + dt); } parser.setTimeZone(tz); String fullDt = year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second; return parser.parse(fullDt); } private String unescape(String input) { return input == null ? null : input.replace("\\;",";").replace("\\,",","); } }