1
0
mirror of https://github.com/etesync/android synced 2024-11-22 16:08:13 +00:00

Refactoring

* WebDavResource: properties in separate subclass
* improve time zone handling
* always provide task list color
This commit is contained in:
Ricki Hirner 2015-08-03 12:16:40 +02:00
parent 5ec4dbb9e7
commit 1c461e9d13
23 changed files with 451 additions and 399 deletions

View File

@ -10,13 +10,9 @@ package at.bitfire.davdroid;
import junit.framework.TestCase;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.RDate;

View File

@ -36,7 +36,7 @@ public class ContactTest extends InstrumentationTestCase {
Contact c = new Contact("test.vcf", null);
// should generate VCard 3.0 by default
assertEquals("text/vcard", c.getMimeType());
assertEquals("text/vcard;charset=UTF-8", c.getMimeType());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:3.0"));
// now let's generate VCard 4.0

View File

@ -9,16 +9,23 @@ package at.bitfire.davdroid.resource;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import android.text.format.Time;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.util.TimeZones;
import java.io.IOException;
import java.io.InputStream;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;
public class EventTest extends InstrumentationTestCase {
protected final TimeZone tzVienna = DateUtils.tzRegistry.getTimeZone("Europe/Vienna");
AssetManager assetMgr;
Event eOnThatDay, eAllDay1Day, eAllDay10Days, eAllDay0Sec;
@ -33,6 +40,21 @@ public class EventTest extends InstrumentationTestCase {
}
public void testGetTzID() throws Exception {
// DATE (without time)
assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new Date("20150101"))));
// DATE-TIME without time zone (floating time): should be UTC (because net.fortuna.ical4j.timezone.date.floating=false)
assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new DateTime("20150101T000000"))));
// DATE-TIME without time zone (UTC)
assertEquals(TimeZones.UTC_ID, Event.getTzId(new DtStart(new DateTime(1438607288000L))));
// DATE-TIME with time zone
assertEquals(tzVienna.getID(), Event.getTzId(new DtStart(new DateTime("20150101T000000", tzVienna))));
}
public void testRecurringWithException() throws Exception {
Event event = parseCalendar("recurring-with-exception1.ics");
assertTrue(event.isAllDay());
@ -55,73 +77,29 @@ public class EventTest extends InstrumentationTestCase {
public void testStartEndTimesAllDay() throws IOException, ParserException {
// event with start date only
assertEquals(868838400000L, eOnThatDay.getDtStartInMillis());
assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtStartTzID());
assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtStartTzID());
// DTEND missing in VEVENT, must have been set to DTSTART+1 day
assertEquals(868838400000L + 86400000, eOnThatDay.getDtEndInMillis());
assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtEndTzID());
assertEquals(TimeZones.UTC_ID, eOnThatDay.getDtEndTzID());
// event with start+end date for all-day event (one day)
assertEquals(868838400000L, eAllDay1Day.getDtStartInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtStartTzID());
assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtStartTzID());
assertEquals(868838400000L + 86400000, eAllDay1Day.getDtEndInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtEndTzID());
assertEquals(TimeZones.UTC_ID, eAllDay1Day.getDtEndTzID());
// event with start+end date for all-day event (ten days)
assertEquals(868838400000L, eAllDay10Days.getDtStartInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtStartTzID());
assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtStartTzID());
assertEquals(868838400000L + 10*86400000, eAllDay10Days.getDtEndInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtEndTzID());
assertEquals(TimeZones.UTC_ID, eAllDay10Days.getDtEndTzID());
// event with start+end date on some day (invalid 0 sec-event)
assertEquals(868838400000L, eAllDay0Sec.getDtStartInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtStartTzID());
assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtStartTzID());
// DTEND invalid in VEVENT, must have been set to DTSTART+1 day
assertEquals(868838400000L + 86400000, eAllDay0Sec.getDtEndInMillis());
assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtEndTzID());
}
public void testTimezoneDefToTzId() {
// test valid definition
final String VTIMEZONE_SAMPLE = // taken from RFC 4791, 5.2.2. CALDAV:calendar-timezone Property
"BEGIN:VCALENDAR\n" +
"PRODID:-//Example Corp.//CalDAV Client//EN\n" +
"VERSION:2.0\n" +
"BEGIN:VTIMEZONE\n" +
"TZID:US-Eastern\n" +
"LAST-MODIFIED:19870101T000000Z\n" +
"BEGIN:STANDARD\n" +
"DTSTART:19671029T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"TZNAME:Eastern Standard Time (US & Canada)\n" +
"END:STANDARD\n" +
"BEGIN:DAYLIGHT\n" +
"DTSTART:19870405T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" +
"TZOFFSETFROM:-0500\n" +
"TZOFFSETTO:-0400\n" +
"TZNAME:Eastern Daylight Time (US & Canada)\n" +
"END:DAYLIGHT\n" +
"END:VTIMEZONE\n" +
"END:VCALENDAR";
assertEquals("US-Eastern", Event.TimezoneDefToTzId(VTIMEZONE_SAMPLE));
// test null value
try {
Event.TimezoneDefToTzId(null);
fail();
} catch(IllegalArgumentException e) {
assert(true);
}
// test invalid time zone
try {
Event.TimezoneDefToTzId("/* invalid content */");
fail();
} catch(IllegalArgumentException e) {
assert(true);
}
assertEquals(TimeZones.UTC_ID, eAllDay0Sec.getDtEndTzID());
}
public void testUnfolding() throws IOException, InvalidResourceException {

View File

@ -25,25 +25,14 @@ import android.test.InstrumentationTestCase;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.DtEnd;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.RDate;
import net.fortuna.ical4j.util.Dates;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import at.bitfire.davdroid.DateUtils;
import lombok.Cleanup;

View File

@ -0,0 +1,95 @@
/*
* Copyright © 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.resource;
import junit.framework.TestCase;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.property.DtStart;
import java.io.StringReader;
import at.bitfire.davdroid.DateUtils;
public class iCalendarTest extends TestCase {
protected final TimeZone tzVienna = DateUtils.tzRegistry.getTimeZone("Europe/Vienna");
public void testTimezoneDefToTzId() {
// test valid definition
assertEquals("US-Eastern", Event.TimezoneDefToTzId("BEGIN:VCALENDAR\n" +
"PRODID:-//Example Corp.//CalDAV Client//EN\n" +
"VERSION:2.0\n" +
"BEGIN:VTIMEZONE\n" +
"TZID:US-Eastern\n" +
"LAST-MODIFIED:19870101T000000Z\n" +
"BEGIN:STANDARD\n" +
"DTSTART:19671029T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"TZNAME:Eastern Standard Time (US & Canada)\n" +
"END:STANDARD\n" +
"BEGIN:DAYLIGHT\n" +
"DTSTART:19870405T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" +
"TZOFFSETFROM:-0500\n" +
"TZOFFSETTO:-0400\n" +
"TZNAME:Eastern Daylight Time (US & Canada)\n" +
"END:DAYLIGHT\n" +
"END:VTIMEZONE\n" +
"END:VCALENDAR"));
// test invalid time zone
assertNull(iCalendar.TimezoneDefToTzId("/* invalid content */"));
// test time zone without TZID
assertNull(iCalendar.TimezoneDefToTzId("BEGIN:VCALENDAR\n" +
"PRODID:-//Inverse inc./SOGo 2.2.10//EN\n" +
"VERSION:2.0\n" +
"END:VCALENDAR"));
}
public void testValidateTimeZone() throws Exception {
assertNotNull(tzVienna);
// date (no time zone) should be ignored
DtStart date = new DtStart(new Date("20150101"));
iCalendar.validateTimeZone(date);
assertNull(date.getTimeZone());
// date-time (Europe/Vienna) should be unchanged
DtStart dtStart = new DtStart("20150101", tzVienna);
iCalendar.validateTimeZone(dtStart);
assertEquals(tzVienna, dtStart.getTimeZone());
// time zone that is not available on Android systems should be changed to system default
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader("BEGIN:VCALENDAR\n" +
"BEGIN:VTIMEZONE\n" +
"TZID:CustomTime\n" +
"BEGIN:STANDARD\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"DTSTART:19600101T000000\n" +
"END:STANDARD\n" +
"END:VTIMEZONE\n" +
"END:VCALENDAR"));
final TimeZone tzCustom = new TimeZone((VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE));
dtStart = new DtStart("20150101T000000", tzCustom);
iCalendar.validateTimeZone(dtStart);
final TimeZone tzDefault = DateUtils.tzRegistry.getTimeZone(java.util.TimeZone.getDefault().getID());
assertNotNull(tzDefault);
assertEquals(tzDefault.getID(), dtStart.getTimeZone().getID());
}
}

View File

@ -18,7 +18,6 @@ import at.bitfire.davdroid.TestConstants;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo;
import ezvcard.VCardVersion;
public class DavResourceFinderTest extends InstrumentationTestCase {

View File

@ -73,7 +73,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
public void testPropfindCurrentUserPrincipal() throws Exception {
davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
assertEquals(new URI("/dav/principals/users/test"), davCollection.getCurrentUserPrincipal());
assertEquals(new URI("/dav/principals/users/test"), davCollection.getProperties().getCurrentUserPrincipal());
WebDavResource simpleFile = new WebDavResource(davAssets, "test.random");
try {
@ -82,14 +82,14 @@ public class WebDavResourceTest extends InstrumentationTestCase {
} catch(DavException ex) {
}
assertNull(simpleFile.getCurrentUserPrincipal());
assertNull(simpleFile.getProperties().getCurrentUserPrincipal());
}
public void testPropfindHomeSets() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "principals/users/test");
dav.propfind(HttpPropfind.Mode.HOME_SETS);
assertEquals(new URI("/dav/addressbooks/test/"), dav.getAddressbookHomeSet());
assertEquals(new URI("/dav/calendars/test/"), dav.getCalendarHomeSet());
assertEquals(new URI("/dav/addressbooks/test/"), dav.getProperties().getAddressbookHomeSet());
assertEquals(new URI("/dav/calendars/test/"), dav.getProperties().getCalendarHomeSet());
}
public void testPropfindAddressBooks() throws Exception {
@ -103,35 +103,46 @@ public class WebDavResourceTest extends InstrumentationTestCase {
WebDavResource ab = dav.getMembers().get(0);
assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/useless-member"), ab.getLocation());
assertEquals("useless-member", ab.getName());
assertFalse(ab.isAddressBook());
assertFalse(ab.getProperties().isAddressBook());
// the second one is an address book (referenced by relative URI)
ab = dav.getMembers().get(1);
assertEquals(TestConstants.roboHydra.resolve("/dav/addressbooks/test/default.vcf/"), ab.getLocation());
assertEquals("default.vcf", ab.getName());
assertTrue(ab.isAddressBook());
assertTrue(ab.getProperties().isAddressBook());
// the third one is an address book (referenced by an absolute URI)
ab = dav.getMembers().get(2);
assertEquals(new URI("https://my.server/absolute:uri/my-address-book/"), ab.getLocation());
assertEquals("my-address-book", ab.getName());
assertTrue(ab.isAddressBook());
assertTrue(ab.getProperties().isAddressBook());
}
public void testPropfindCalendars() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "calendars/test");
dav.propfind(Mode.CALDAV_COLLECTIONS);
assertEquals(3, dav.getMembers().size());
assertEquals("0xFF00FF", dav.getMembers().get(2).getColor());
assertEquals(new Integer(0xFFFF00FF), dav.getMembers().get(2).getProperties().getColor());
for (WebDavResource member : dav.getMembers()) {
if (member.getName().contains(".ics"))
assertTrue(member.isCalendar());
assertTrue(member.getProperties().isCalendar());
else
assertFalse(member.isCalendar());
assertFalse(member.isAddressBook());
assertFalse(member.getProperties().isCalendar());
assertFalse(member.getProperties().isAddressBook());
}
}
public void testPropfindCollectionProperties() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "propfind-collection-properties");
dav.propfind(Mode.COLLECTION_PROPERTIES);
assertTrue(dav.members.isEmpty());
assertTrue(dav.properties.isCollection);
assertTrue(dav.properties.isAddressBook);
assertNull(dav.properties.displayName);
assertNull(dav.properties.color);
assertEquals(VCardVersion.V4_0, dav.properties.supportedVCardVersion);
}
public void testPropfindTrailingSlashes() throws Exception {
final String principalOK = "/principals/ok";
@ -145,7 +156,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
for (String path : requestPaths) {
WebDavResource davSlash = new WebDavResource(davCollection, new URI(path));
davSlash.propfind(Mode.CARDDAV_COLLECTIONS);
assertEquals(new URI(principalOK), davSlash.getCurrentUserPrincipal());
assertEquals(new URI(principalOK), davSlash.getProperties().getCurrentUserPrincipal());
}
}
@ -190,7 +201,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf/");
davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[] { "1.vcf", "2:3@my%40pc.vcf" });
// queried address book has a name
assertEquals("My Book", davAddressBook.getDisplayName());
assertEquals("My Book", davAddressBook.getProperties().getDisplayName());
// there are two contacts
assertEquals(2, davAddressBook.getMembers().size());
// contact file names should be unescaped (yes, it's really named ...%40pc... to check double-encoding)

View File

@ -81,6 +81,40 @@ exports.getBodyParts = function(conf) {
}
}
}),
new RoboHydraHeadDAV({
path: "/dav/propfind-collection-properties",
handler: function(req,res,next) {
if (req.method == "PROPFIND") {
res.statusCode = 207;
res.write('\<?xml version="1.0" encoding="utf-8" ?>\
<multistatus xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
<response>\
<href>/dav/propfind-collection-properties</href> \
<propstat>\
<prop>\
<resourcetype>\
<collection/>\
<CARD:addressbook/>\
</resourcetype>\
<CARD:supported-address-data>\
<address-data-type content-type="text/vcard" version="4.0"/>\
</CARD:supported-address-data>\
</prop>\
<status>HTTP/1.1 200 OK</status>\
</propstat>\
<propstat>\
<prop>\
<displayname/>\
<A:calendar-color xmlns:A="http://apple.com/ns/ical/">0xFF00FF</A:calendar-color>\
</prop>\
<status>HTTP/1.1 404 Not Found</status>\
</propstat>\
</response>\
</multistatus>\
');
}
}
}),
/* principal URL */
new RoboHydraHeadDAV({

View File

@ -16,8 +16,11 @@ import java.util.regex.Pattern;
public class DAVUtils {
private static final String TAG = "davdroid.DAVutils";
public static final int calendarGreen = 0xFFC3EA6E;
public static int CalDAVtoARGBColor(String davColor) {
int color = 0xFFC3EA6E; // fallback: "DAVdroid green"
int color = calendarGreen; // fallback: "DAVdroid green"
if (davColor != null) {
Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?");
Matcher m = p.matcher(davColor);

View File

@ -8,33 +8,25 @@
package at.bitfire.davdroid;
import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.ExDate;
import net.fortuna.ical4j.model.property.RDate;
import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.SimpleTimeZone;
import java.util.StringTokenizer;
public class DateUtils {
private final static String TAG = "davdroid.DateUtils";
@ -49,35 +41,35 @@ public class DateUtils {
// time zones
public static String findAndroidTimezoneID(String tzID) {
String localTZ = null;
public static String findAndroidTimezoneID(String tz) {
String deviceTZ = null;
String availableTZs[] = SimpleTimeZone.getAvailableIDs();
// first, try to find an exact match (case insensitive)
for (String availableTZ : availableTZs)
if (availableTZ.equalsIgnoreCase(tzID)) {
localTZ = availableTZ;
if (availableTZ.equalsIgnoreCase(tz)) {
deviceTZ = availableTZ;
break;
}
// if that doesn't work, try to find something else that matches
if (localTZ == null) {
if (deviceTZ == null) {
Log.w(TAG, "Coulnd't find time zone with matching identifiers, trying to guess");
for (String availableTZ : availableTZs)
if (StringUtils.indexOfIgnoreCase(tzID, availableTZ) != -1) {
localTZ = availableTZ;
if (StringUtils.indexOfIgnoreCase(tz, availableTZ) != -1) {
deviceTZ = availableTZ;
break;
}
}
// if that doesn't work, use UTC as fallback
if (localTZ == null) {
Log.e(TAG, "Couldn't identify time zone, using UTC as fallback");
localTZ = Time.TIMEZONE_UTC;
if (deviceTZ == null) {
final String defaultTZ = TimeZone.getDefault().getID();
Log.e(TAG, "Couldn't identify time zone, using system default (" + defaultTZ + ") as fallback");
deviceTZ = defaultTZ;
}
Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID);
return localTZ;
return deviceTZ;
}

View File

@ -33,7 +33,6 @@ import at.bitfire.davdroid.webdav.DavIncapableException;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import at.bitfire.davdroid.webdav.NotAuthorizedException;
import at.bitfire.davdroid.webdav.WebDavResource;
import ezvcard.VCardVersion;
public class DavResourceFinder implements Closeable {
private final static String TAG = "davdroid.ResourceFinder";
@ -62,7 +61,7 @@ public class DavResourceFinder implements Closeable {
URI uriAddressBookHomeSet = null;
try {
principal.propfind(Mode.HOME_SETS);
uriAddressBookHomeSet = principal.getAddressbookHomeSet();
uriAddressBookHomeSet = principal.getProperties().getAddressbookHomeSet();
} catch (Exception e) {
Log.i(TAG, "Couldn't find address-book home set", e);
}
@ -80,19 +79,21 @@ public class DavResourceFinder implements Closeable {
possibleAddressBooks.addAll(homeSetAddressBooks.getMembers());
List<ServerInfo.ResourceInfo> addressBooks = new LinkedList<>();
for (WebDavResource resource : possibleAddressBooks)
if (resource.isAddressBook()) {
for (WebDavResource resource : possibleAddressBooks) {
final WebDavResource.Properties properties = resource.getProperties();
if (properties.isAddressBook()) {
Log.i(TAG, "Found address book: " + resource.getLocation().getPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
resource.isReadOnly(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
properties.isReadOnly(),
resource.getLocation().toString(),
properties.getDisplayName(),
properties.getDescription(), properties.getColor()
);
addressBooks.add(info);
}
}
serverInfo.setAddressBooks(addressBooks);
} else
Log.w(TAG, "Found address-book home set, but it doesn't advertise CardDAV support");
@ -104,7 +105,7 @@ public class DavResourceFinder implements Closeable {
URI uriCalendarHomeSet = null;
try {
principal.propfind(Mode.HOME_SETS);
uriCalendarHomeSet = principal.getCalendarHomeSet();
uriCalendarHomeSet = principal.getProperties().getCalendarHomeSet();
} catch(Exception e) {
Log.i(TAG, "Couldn't find calendar home set", e);
}
@ -124,27 +125,28 @@ public class DavResourceFinder implements Closeable {
List<ServerInfo.ResourceInfo>
calendars = new LinkedList<>(),
todoLists = new LinkedList<>();
for (WebDavResource resource : possibleCalendars)
if (resource.isCalendar()) {
for (WebDavResource resource : possibleCalendars) {
final WebDavResource.Properties properties = resource.getProperties();
if (properties.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
properties.isReadOnly(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
properties.getDisplayName(),
properties.getDescription(), properties.getColor()
);
info.setTimezone(resource.getTimezone());
info.setTimezone(properties.getTimeZone());
boolean isCalendar = false,
isTodoList = false;
if (resource.getSupportedComponents() == null) {
if (properties.getSupportedComponents() == null) {
// no info about supported components, assuming all components are supported
isCalendar = true;
isTodoList = true;
} else {
// CALDAV:supported-calendar-component-set available
for (String supportedComponent : resource.getSupportedComponents())
for (String supportedComponent : properties.getSupportedComponents())
if ("VEVENT".equalsIgnoreCase(supportedComponent))
isCalendar = true;
else if ("VTODO".equalsIgnoreCase(supportedComponent))
@ -162,6 +164,7 @@ public class DavResourceFinder implements Closeable {
if (isTodoList)
todoLists.add(new ServerInfo.ResourceInfo(info));
}
}
serverInfo.setCalendars(calendars);
serverInfo.setTodoLists(todoLists);
@ -271,8 +274,8 @@ public class DavResourceFinder implements Closeable {
try {
WebDavResource wellKnown = new WebDavResource(base, "/.well-known/" + serviceName);
wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (wellKnown.getCurrentUserPrincipal() != null) {
URI principal = wellKnown.getCurrentUserPrincipal();
if (wellKnown.getProperties().getCurrentUserPrincipal() != null) {
URI principal = wellKnown.getProperties().getCurrentUserPrincipal();
Log.i(TAG, "Principal URL found from Well-Known URI: " + principal);
return new WebDavResource(wellKnown, principal);
}
@ -293,8 +296,8 @@ public class DavResourceFinder implements Closeable {
Log.d(TAG, "Well-known service detection failed, trying initial context path " + initialURL);
try {
base.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (base.getCurrentUserPrincipal() != null) {
URI principal = base.getCurrentUserPrincipal();
if (base.getProperties().getCurrentUserPrincipal() != null) {
URI principal = base.getProperties().getCurrentUserPrincipal();
Log.i(TAG, "Principal URL found from initial context path: " + principal);
return new WebDavResource(base, principal);
}

View File

@ -17,15 +17,11 @@ import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.PropertyList;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.DateProperty;
@ -46,14 +42,11 @@ import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Transp;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import net.fortuna.ical4j.util.TimeZones;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedList;
@ -63,7 +56,6 @@ import java.util.TimeZone;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.DateUtils;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
@ -156,15 +148,13 @@ public class Event extends iCalendar {
if ((dtStart = event.getStartDate()) == null || (dtEnd = event.getEndDate()) == null)
throw new InvalidResourceException("Invalid start time/end time/duration");
if (hasTime(dtStart)) {
validateTimeZone(dtStart);
validateTimeZone(dtEnd);
}
validateTimeZone(dtStart);
validateTimeZone(dtEnd);
// all-day events and "events on that day":
// * related UNIX times must be in UTC
// * must have a duration (set to one day if missing)
if (!hasTime(dtStart) && !dtEnd.getDate().after(dtStart.getDate())) {
if (!isDateTime(dtStart) && !dtEnd.getDate().after(dtStart.getDate())) {
Log.i(TAG, "Repairing iCal: DTEND := DTSTART+1");
Calendar c = Calendar.getInstance(TimeZone.getTimeZone(Time.TIMEZONE_UTC));
c.setTime(dtStart.getDate());
@ -304,8 +294,20 @@ public class Event extends iCalendar {
// time helpers
/**
* Returns the time-zone ID for a given date-time, or TIMEZONE_UTC for dates (without time).
* TIMEZONE_UTC is also returned for DATE-TIMEs in UTC representation.
* @param date DateProperty (DATE or DATE-TIME) whose time-zone information is used
*/
protected static String getTzId(DateProperty date) {
if (isDateTime(date) && !date.isUtc() && date.getTimeZone() != null)
return date.getTimeZone().getID();
else
return TimeZones.UTC_ID;
}
public boolean isAllDay() {
return !hasTime(dtStart);
return !isDateTime(dtStart);
}
public long getDtStartInMillis() {

View File

@ -17,7 +17,6 @@ import android.content.Entity;
import android.content.EntityIterator;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
@ -48,7 +47,6 @@ import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -127,9 +125,9 @@ public class LocalAddressBook extends LocalCollection<Contact> {
}
@Override
public void updateMetaData(WebDavResource resource)
public void updateMetaData(WebDavResource.Properties properties)
{
final VCardVersion vCardVersion = resource.getVCardVersion();
final VCardVersion vCardVersion = properties.getSupportedVCardVersion();
accountSettings.setAddressBookVCardVersion(vCardVersion != null ? vCardVersion : VCardVersion.V3_0);
}

View File

@ -22,7 +22,6 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Attendees;
@ -42,10 +41,8 @@ import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.CuType;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Role;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.Action;
import net.fortuna.ical4j.model.property.Attendee;
import net.fortuna.ical4j.model.property.DateListProperty;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.Duration;
import net.fortuna.ical4j.model.property.ExDate;
@ -61,10 +58,8 @@ import org.apache.commons.lang3.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.DateUtils;
@ -119,7 +114,7 @@ public class LocalCalendar extends LocalCollection<Event> {
values.put(Calendars.ACCOUNT_TYPE, account.type);
values.put(Calendars.NAME, info.getURL());
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle());
values.put(Calendars.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
values.put(Calendars.CALENDAR_COLOR, info.getColor() != null ? info.getColor() : DAVUtils.calendarGreen);
values.put(Calendars.OWNER_ACCOUNT, account.name);
values.put(Calendars.SYNC_EVENTS, 1);
values.put(Calendars.VISIBLE, 1);
@ -139,7 +134,7 @@ public class LocalCalendar extends LocalCollection<Event> {
}
if (info.getTimezone() != null)
values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone());
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(info.getTimezone()));
Log.i(TAG, "Inserting calendar: " + values.toString());
try {
@ -196,16 +191,16 @@ public class LocalCalendar extends LocalCollection<Event> {
}
@Override
public void updateMetaData(WebDavResource resource) throws LocalStorageException {
public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException {
ContentValues values = new ContentValues();
final String displayName = resource.getDisplayName();
final String displayName = properties.getDisplayName();
if (displayName != null)
values.put(Calendars.CALENDAR_DISPLAY_NAME, displayName);
final String color = resource.getColor();
final Integer color = properties.getColor();
if (color != null)
values.put(Calendars.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(color));
values.put(Calendars.CALENDAR_COLOR, color);
try {
if (values.size() > 0)

View File

@ -18,7 +18,6 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
@ -26,7 +25,6 @@ import android.util.Log;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -96,7 +94,7 @@ public abstract class LocalCollection<T extends Resource> {
/** gets the CTag of the collection */
abstract public String getCTag() throws LocalStorageException;
/** update locally stored collection properties (e.g. display name and color) from a WebDavResource */
abstract public void updateMetaData(WebDavResource resource) throws LocalStorageException;
abstract public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException;
// content provider (= database) querying

View File

@ -18,7 +18,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.util.Log;
import net.fortuna.ical4j.model.Date;
@ -36,7 +35,6 @@ import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang3.StringUtils;
import org.dmfs.provider.tasks.TaskContract;
import java.util.HashMap;
import java.util.LinkedList;
import at.bitfire.davdroid.DAVUtils;
@ -77,7 +75,7 @@ public class LocalTaskList extends LocalCollection<Task> {
values.put(TaskContract.TaskLists.ACCOUNT_TYPE, account.type);
values.put(TaskContract.TaskLists._SYNC_ID, info.getURL());
values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle());
values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor()));
values.put(TaskContract.TaskLists.LIST_COLOR, info.getColor() != null ? info.getColor() : DAVUtils.calendarGreen);
values.put(TaskContract.TaskLists.OWNER, account.name);
values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0);
values.put(TaskContract.TaskLists.SYNC_ENABLED, 1);
@ -135,16 +133,16 @@ public class LocalTaskList extends LocalCollection<Task> {
}
@Override
public void updateMetaData(WebDavResource resource) throws LocalStorageException {
public void updateMetaData(WebDavResource.Properties properties) throws LocalStorageException {
ContentValues values = new ContentValues();
final String displayName = resource.getDisplayName();
final String displayName = properties.getDisplayName();
if (displayName != null)
values.put(TaskContract.TaskLists.LIST_NAME, displayName);
final String color = resource.getColor();
final Integer color = properties.getColor();
if (color != null)
values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(color));
values.put(TaskContract.TaskLists.LIST_COLOR, color);
try {
if (values.size() > 0)

View File

@ -13,7 +13,6 @@ import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import ezvcard.VCardVersion;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@ -56,8 +55,8 @@ public class ServerInfo implements Serializable {
final String URL, // absolute URL of resource
title,
description,
color;
description;
final Integer color;
String timezone;

View File

@ -35,8 +35,6 @@ import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Url;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -45,7 +43,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.Setter;

View File

@ -88,7 +88,7 @@ public abstract class WebDavCollection<T extends Resource> {
List<T> resources = new LinkedList<>();
if (collection.getMembers() != null)
for (WebDavResource member : collection.getMembers())
resources.add(newResourceSkeleton(member.getName(), member.getETag()));
resources.add(newResourceSkeleton(member.getName(), member.getProperties().getETag()));
return resources.toArray(new Resource[resources.size()]);
@ -113,7 +113,7 @@ public abstract class WebDavCollection<T extends Resource> {
throw new DavNoContentException();
for (WebDavResource member : collection.getMembers()) {
T resource = newResourceSkeleton(member.getName(), member.getETag());
T resource = newResourceSkeleton(member.getName(), member.getProperties().getETag());
try {
if (member.getContent() != null) {
@Cleanup InputStream is = new ByteArrayInputStream(member.getContent());
@ -158,13 +158,13 @@ public abstract class WebDavCollection<T extends Resource> {
// returns ETag of the created resource, if returned by server
public String add(Resource res) throws URISyntaxException, IOException, HttpException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(res.getMimeType());
member.getProperties().setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.ADD_DONT_OVERWRITE);
// after a successful upload, the collection has implicitely changed, too
collection.invalidateCTag();
collection.getProperties().invalidateCTag();
return eTag;
}
@ -173,19 +173,19 @@ public abstract class WebDavCollection<T extends Resource> {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.delete();
collection.invalidateCTag();
collection.getProperties().invalidateCTag();
}
// returns ETag of the updated resource, if returned by server
public String update(Resource res) throws URISyntaxException, IOException, HttpException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(res.getMimeType());
member.getProperties().setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.UPDATE_DONT_OVERWRITE);
// after a successful upload, the collection has implicitely changed, too
collection.invalidateCTag();
collection.getProperties().invalidateCTag();
return eTag;
}

View File

@ -8,29 +8,24 @@
package at.bitfire.davdroid.resource;
import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateProperty;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.TimeZone;
import at.bitfire.davdroid.DateUtils;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.NonNull;
public abstract class iCalendar extends Resource {
static private final String TAG = "DAVdroid.iCal";
@ -72,47 +67,46 @@ public abstract class iCalendar extends Resource {
// time zone helpers
protected static boolean hasTime(DateProperty date) {
protected static boolean isDateTime(DateProperty date) {
return date.getDate() instanceof DateTime;
}
protected static String getTzId(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return Time.TIMEZONE_UTC;
else if (date.getTimeZone() != null)
return date.getTimeZone().getID();
else if (date.getParameter(Value.TZID) != null)
return date.getParameter(Value.TZID).getValue();
// fallback
return Time.TIMEZONE_UTC;
}
/* guess matching Android timezone ID */
/**
* Ensures that a given DateProperty has a time zone with an ID that is available in Android.
* @param date DateProperty to validate. Values which are not DATE-TIME will be ignored.
*/
protected static void validateTimeZone(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return;
if (isDateTime(date)) {
final TimeZone tz = date.getTimeZone();
if (tz == null)
return;
final String tzID = tz.getID();
if (tzID == null)
return;
String tzID = getTzId(date);
if (tzID == null)
return;
String localTZ = DateUtils.findAndroidTimezoneID(tzID);
date.setTimeZone(DateUtils.tzRegistry.getTimeZone(localTZ));
String deviceTzID = DateUtils.findAndroidTimezoneID(tzID);
if (!tzID.equals(deviceTzID))
date.setTimeZone(DateUtils.tzRegistry.getTimeZone(deviceTzID));
}
}
public static String TimezoneDefToTzId(String timezoneDef) throws IllegalArgumentException {
/**
* Takes a string with a timezone definition and returns the time zone ID.
* @param timezoneDef time zone definition (VCALENDAR with VTIMEZONE component)
* @return time zone id (TZID) if VTIMEZONE contains a TZID,
* null otherwise
*/
public static String TimezoneDefToTzId(@NonNull String timezoneDef) {
try {
if (timezoneDef != null) {
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
if (timezone != null && timezone.getTimeZoneId() != null)
return timezone.getTimeZoneId().getValue();
}
} catch (Exception ex) {
Log.w(TAG, "Can't understand time zone definition, ignoring", ex);
} catch (IOException|ParserException e) {
Log.e(TAG, "Can't understand time zone definition", e);
}
throw new IllegalArgumentException();
return null;
}
}

View File

@ -19,8 +19,8 @@ import at.bitfire.davdroid.ArrayUtils;
import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalStorageException;
import at.bitfire.davdroid.resource.RecordNotFoundException;
import at.bitfire.davdroid.resource.WebDavCollection;
import at.bitfire.davdroid.resource.Resource;
import at.bitfire.davdroid.resource.WebDavCollection;
import at.bitfire.davdroid.webdav.ConflictException;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.HttpException;
@ -46,8 +46,8 @@ public class SyncManager {
public void synchronize(boolean manualSync, SyncResult syncResult) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
// PHASE 1: fetch collection properties
remote.getProperties();
final WebDavResource collectionResource = remote.getCollection();
local.updateMetaData(collectionResource);
final WebDavResource.Properties collectionProperties = remote.getCollection().getProperties();
local.updateMetaData(collectionProperties);
// PHASE 2: push local changes to server
int deletedRemotely = pushDeleted(),
@ -62,7 +62,7 @@ public class SyncManager {
}
if (!syncMembers) {
final String
currentCTag = collectionResource.getCTag(),
currentCTag = collectionProperties.getCTag(),
lastCTag = local.getCTag();
Log.d(TAG, "Last local CTag = " + lastCTag + "; current remote CTag = " + currentCTag);
if (currentCTag == null || !currentCTag.equals(lastCTag))
@ -101,7 +101,7 @@ public class SyncManager {
// update collection CTag
Log.i(TAG, "Sync complete, fetching new CTag");
local.setCTag(collectionResource.getCTag());
local.setCTag(collectionProperties.getCTag());
}

View File

@ -9,7 +9,6 @@
package at.bitfire.davdroid.ui.setup;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;

View File

@ -41,18 +41,19 @@ import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import at.bitfire.davdroid.DAVUtils;
import at.bitfire.davdroid.URIUtils;
import at.bitfire.davdroid.resource.Event;
import at.bitfire.davdroid.resource.iCalendar;
import at.bitfire.davdroid.webdav.DavProp.Comp;
import ezvcard.VCardVersion;
import lombok.Cleanup;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ -64,15 +65,6 @@ import lombok.ToString;
public class WebDavResource {
private static final String TAG = "davdroid.WebDavResource";
public enum Property {
CURRENT_USER_PRINCIPAL, // resource detection
ADDRESSBOOK_HOMESET, CALENDAR_HOMESET,
CONTENT_TYPE, READ_ONLY, // WebDAV (common)
DISPLAY_NAME, DESCRIPTION, ETAG,
IS_COLLECTION, CTAG, // collections
IS_CALENDAR, COLOR, TIMEZONE, // CalDAV
IS_ADDRESSBOOK, VCARD_VERSION // CardDAV
}
public enum PutMode {
ADD_DONT_OVERWRITE,
UPDATE_DONT_OVERWRITE
@ -85,11 +77,8 @@ public class WebDavResource {
protected Set<String> capabilities = new HashSet<>(),
methods = new HashSet<>();
// DAV properties
protected HashMap<Property, String> properties = new HashMap<>();
@Getter protected List<String> supportedComponents;
// list of members (only for collections)
@Getter Properties properties = new Properties();
@Getter protected List<WebDavResource> members;
// content (available after GET)
@ -151,9 +140,9 @@ public class WebDavResource {
location = parent.location.resolve(new URI(null, null, "./" + member, null));
}
public WebDavResource(WebDavResource parent, String member, String ETag) throws URISyntaxException {
public WebDavResource(WebDavResource parent, String member, String eTag) throws URISyntaxException {
this(parent, member);
properties.put(Property.ETAG, ETag);
properties.eTag = eTag;
}
@ -191,76 +180,6 @@ public class WebDavResource {
}
/* property methods */
public URI getCurrentUserPrincipal() throws URISyntaxException {
String principal = properties.get(Property.CURRENT_USER_PRINCIPAL);
return principal != null ? URIUtils.parseURI(principal, false) : null;
}
public URI getAddressbookHomeSet() throws URISyntaxException {
String homeset = properties.get(Property.ADDRESSBOOK_HOMESET);
return homeset != null ? URIUtils.parseURI(homeset, false) : null;
}
public URI getCalendarHomeSet() throws URISyntaxException {
String homeset = properties.get(Property.CALENDAR_HOMESET);
return homeset != null ? URIUtils.parseURI(homeset, false) : null;
}
public String getContentType() {
return properties.get(Property.CONTENT_TYPE);
}
public void setContentType(String mimeType) {
properties.put(Property.CONTENT_TYPE, mimeType);
}
public boolean isReadOnly() {
return properties.containsKey(Property.READ_ONLY);
}
public String getDisplayName() {
return properties.get(Property.DISPLAY_NAME);
}
public String getDescription() {
return properties.get(Property.DESCRIPTION);
}
public String getCTag() {
return properties.get(Property.CTAG);
}
public void invalidateCTag() {
properties.remove(Property.CTAG);
}
public String getETag() {
return properties.get(Property.ETAG);
}
public boolean isCalendar() {
return properties.containsKey(Property.IS_CALENDAR);
}
public String getColor() {
return properties.get(Property.COLOR);
}
public String getTimezone() {
return properties.get(Property.TIMEZONE);
}
public boolean isAddressBook() {
return properties.containsKey(Property.IS_ADDRESSBOOK);
}
public VCardVersion getVCardVersion() {
String versionStr = properties.get(Property.VCARD_VERSION);
return (versionStr != null) ? VCardVersion.valueOfByStr(versionStr) : null;
}
/* collection operations */
public void propfind(HttpPropfind.Mode mode) throws URISyntaxException, IOException, DavException, HttpException {
@ -372,12 +291,12 @@ public class WebDavResource {
put.addHeader("If-None-Match", "*");
break;
case UPDATE_DONT_OVERWRITE:
put.addHeader("If-Match", (getETag() != null) ? getETag() : "*");
put.addHeader("If-Match", (properties.eTag != null) ? properties.eTag : "*");
break;
}
if (getContentType() != null)
put.addHeader("Content-Type", getContentType());
if (properties.contentType != null)
put.addHeader("Content-Type", properties.contentType);
@Cleanup CloseableHttpResponse response = httpClient.execute(put, context);
checkResponse(response);
@ -392,8 +311,8 @@ public class WebDavResource {
public void delete() throws URISyntaxException, IOException, HttpException {
HttpDeleteHC4 delete = new HttpDeleteHC4(location);
if (getETag() != null)
delete.addHeader("If-Match", getETag());
if (properties.eTag != null)
delete.addHeader("If-Match", properties.eTag);
@Cleanup CloseableHttpResponse response = httpClient.execute(delete, context);
checkResponse(response);
@ -473,8 +392,7 @@ public class WebDavResource {
Log.d(TAG, "Processing multi-status element: " + href);
// process known properties
HashMap<Property, String> properties = new HashMap<>();
List<String> supportedComponents = null;
Properties properties = new Properties();
byte[] data = null;
// in <response>, either <status> or <propstat> must be present
@ -488,84 +406,9 @@ public class WebDavResource {
// ignore information about missing properties etc.
if (status.getStatusCode()/100 != 1 && status.getStatusCode()/100 != 2)
continue;
DavProp prop = singlePropstat.prop;
if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href);
if (prop.currentUserPrivilegeSet != null) {
// privilege info available
boolean mayAll = false,
mayBind = false,
mayUnbind = false,
mayWrite = false,
mayWriteContent = false;
for (DavProp.Privilege privilege : prop.currentUserPrivilegeSet) {
if (privilege.getAll() != null) mayAll = true;
if (privilege.getBind() != null) mayBind = true;
if (privilege.getUnbind() != null) mayUnbind = true;
if (privilege.getWrite() != null) mayWrite = true;
if (privilege.getWriteContent() != null) mayWriteContent = true;
}
if (!mayAll && !mayWrite && !(mayWriteContent && mayBind && mayUnbind))
properties.put(Property.READ_ONLY, "1");
}
if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
properties.put(Property.ADDRESSBOOK_HOMESET, URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href));
if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
properties.put(Property.CALENDAR_HOMESET, URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href));
if (prop.displayname != null)
properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName());
if (prop.resourcetype != null) {
if (prop.resourcetype.getCollection() != null) {
properties.put(Property.IS_COLLECTION, "1");
// is a collection, ensure trailing slash
href = URIUtils.ensureTrailingSlash(href);
}
if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
properties.put(Property.IS_ADDRESSBOOK, "1");
if (prop.addressbookDescription != null)
properties.put(Property.DESCRIPTION, prop.addressbookDescription.getDescription());
if (prop.supportedAddressData != null)
for (DavProp.AddressDataType dataType : prop.supportedAddressData)
if ("text/vcard".equalsIgnoreCase(dataType.getContentType()))
// ignore "3.0" as it MUST be supported anyway
if ("4.0".equals(dataType.getVersion()))
properties.put(Property.VCARD_VERSION, VCardVersion.V4_0.getVersion());
}
if (prop.resourcetype.getCalendar() != null) { // CalDAV collection propertioes
properties.put(Property.IS_CALENDAR, "1");
if (prop.calendarDescription != null)
properties.put(Property.DESCRIPTION, prop.calendarDescription.getDescription());
if (prop.calendarColor != null)
properties.put(Property.COLOR, prop.calendarColor.getColor());
if (prop.calendarTimezone != null)
try {
properties.put(Property.TIMEZONE, Event.TimezoneDefToTzId(prop.calendarTimezone.getTimezone()));
} catch(IllegalArgumentException e) {
}
if (prop.supportedCalendarComponentSet != null) {
supportedComponents = new LinkedList<>();
for (Comp component : prop.supportedCalendarComponentSet)
supportedComponents.add(component.getName());
}
}
}
if (prop.getctag != null)
properties.put(Property.CTAG, prop.getctag.getCTag());
if (prop.getetag != null)
properties.put(Property.ETAG, prop.getetag.getETag());
properties.process(prop);
if (prop.calendarData != null && prop.calendarData.ical != null)
data = prop.calendarData.ical.getBytes();
@ -574,16 +417,16 @@ public class WebDavResource {
}
// about which resource is this response?
if (properties.isCollection) // ensure trailing slashs for collections
href = URIUtils.ensureTrailingSlash(href);
if (location.equals(href) || URIUtils.ensureTrailingSlash(location).equals(href)) { // about ourselves
this.properties.putAll(properties);
if (supportedComponents != null)
this.supportedComponents = supportedComponents;
this.properties = properties;
this.content = data;
} else { // about a member
WebDavResource member = new WebDavResource(this, href);
member.properties = properties;
member.supportedComponents = supportedComponents;
member.content = data;
members.add(member);
@ -593,4 +436,133 @@ public class WebDavResource {
this.members = members;
}
public static class Properties {
// DAV properties
protected String
currentUserPrincipal,
addressBookHomeset,
calendarHomeset,
color;
@Getter protected String
displayName,
description,
timeZone,
eTag,
cTag;
@Getter @Setter protected String contentType;
@Getter protected boolean
readOnly,
isCollection,
isCalendar,
isAddressBook;
@Getter protected List<String> supportedComponents;
@Getter protected VCardVersion supportedVCardVersion;
// fill from DavProp
protected void process(DavProp prop) {
if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
currentUserPrincipal = prop.currentUserPrincipal.getHref().href;
if (prop.currentUserPrivilegeSet != null) {
// privilege info available
boolean mayAll = false,
mayBind = false,
mayUnbind = false,
mayWrite = false,
mayWriteContent = false;
for (DavProp.Privilege privilege : prop.currentUserPrivilegeSet) {
if (privilege.getAll() != null) mayAll = true;
if (privilege.getBind() != null) mayBind = true;
if (privilege.getUnbind() != null) mayUnbind = true;
if (privilege.getWrite() != null) mayWrite = true;
if (privilege.getWriteContent() != null) mayWriteContent = true;
}
if (!mayAll && !mayWrite && !(mayWriteContent && mayBind && mayUnbind))
readOnly = true;
}
if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
addressBookHomeset = URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href);
if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
calendarHomeset = URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href);
if (prop.displayname != null)
displayName = prop.displayname.getDisplayName();
if (prop.resourcetype != null) {
if (prop.resourcetype.getCollection() != null)
isCollection = true;
if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
isAddressBook = true;
if (prop.addressbookDescription != null)
description = prop.addressbookDescription.getDescription();
if (prop.supportedAddressData != null)
for (DavProp.AddressDataType dataType : prop.supportedAddressData)
if ("text/vcard".equalsIgnoreCase(dataType.getContentType()))
// ignore "3.0" as it MUST be supported anyway
if ("4.0".equals(dataType.getVersion()))
supportedVCardVersion = VCardVersion.V4_0;
}
if (prop.resourcetype.getCalendar() != null) { // CalDAV collection propertioes
isCalendar = true;
if (prop.calendarDescription != null)
description = prop.calendarDescription.getDescription();
if (prop.calendarColor != null)
color = prop.calendarColor.getColor();
if (prop.calendarTimezone != null)
timeZone = prop.calendarTimezone.getTimezone();
if (prop.supportedCalendarComponentSet != null) {
supportedComponents = new LinkedList<>();
for (Comp component : prop.supportedCalendarComponentSet)
supportedComponents.add(component.getName());
}
}
}
if (prop.getctag != null)
cTag = prop.getctag.getCTag();
if (prop.getetag != null)
eTag = prop.getetag.getETag();
}
// getters / setters
public Integer getColor() {
return color != null ? DAVUtils.CalDAVtoARGBColor(color) : null;
}
public URI getCurrentUserPrincipal() throws URISyntaxException {
return currentUserPrincipal != null ? URIUtils.parseURI(currentUserPrincipal, false) : null;
}
public URI getAddressbookHomeSet() throws URISyntaxException {
return addressBookHomeset != null ? URIUtils.parseURI(addressBookHomeset, false) : null;
}
public URI getCalendarHomeSet() throws URISyntaxException {
return calendarHomeset != null ? URIUtils.parseURI(calendarHomeset, false) : null;
}
public String getTimeZone() {
return timeZone != null ? iCalendar.TimezoneDefToTzId(timeZone) : null;
}
public void invalidateCTag() {
cTag = null;
}
}
}