1
0
mirror of https://github.com/etesync/android synced 2025-01-11 08:10:58 +00:00

Version bump to 0.5.3-alpha

* add null checks (should fix #117)
* more detailed DAV exceptions for error handling
* better logging (limited to 10 kB per log entry) to avoid memory problems
* DavMultiget creates requests itself (instead of WebDavResource)
This commit is contained in:
rfc2822 2013-12-15 19:45:28 +01:00
parent ec4fedf04e
commit 6cfaad35b1
18 changed files with 246 additions and 143 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid" package="at.bitfire.davdroid"
android:versionCode="20" android:versionCode="21"
android:versionName="0.5.2-alpha" > android:versionName="0.5.3-alpha" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="14"

View File

@ -0,0 +1,58 @@
package at.bitfire.davdroid;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
public class LoggingInputStream extends FilterInputStream {
private static final int MAX_LENGTH = 10*1024; // don't log more than 10 kB of data
String tag;
ByteArrayOutputStream log = new ByteArrayOutputStream(MAX_LENGTH);
int logSize = 0;
public LoggingInputStream(String tag, InputStream proxy) {
super(proxy);
this.tag = tag;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read() throws IOException {
int b = super.read();
if (logSize < MAX_LENGTH) {
log.write(b);
logSize++;
}
return b;
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount)
throws IOException {
int read = super.read(buffer, byteOffset, byteCount);
int bytesToLog = read;
if (bytesToLog + logSize > MAX_LENGTH)
bytesToLog = MAX_LENGTH - logSize;
if (bytesToLog > 0) {
log.write(buffer, byteOffset, bytesToLog);
logSize += bytesToLog;
}
return read;
}
@Override
public void close() throws IOException {
Log.d(tag, "Content: " + log.toString());
super.close();
}
}

View File

@ -9,7 +9,7 @@ package at.bitfire.davdroid.resource;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import at.bitfire.davdroid.webdav.WebDavResource.MultigetType; import at.bitfire.davdroid.webdav.DavMultiget;
public class CalDavCalendar extends RemoteCollection<Event> { public class CalDavCalendar extends RemoteCollection<Event> {
//private final static String TAG = "davdroid.CalDavCalendar"; //private final static String TAG = "davdroid.CalDavCalendar";
@ -20,8 +20,8 @@ public class CalDavCalendar extends RemoteCollection<Event> {
} }
@Override @Override
protected MultigetType multiGetType() { protected DavMultiget.Type multiGetType() {
return MultigetType.CALENDAR; return DavMultiget.Type.CALENDAR;
} }
@Override @Override

View File

@ -9,7 +9,7 @@ package at.bitfire.davdroid.resource;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import at.bitfire.davdroid.webdav.WebDavResource.MultigetType; import at.bitfire.davdroid.webdav.DavMultiget;
public class CardDavAddressBook extends RemoteCollection<Contact> { public class CardDavAddressBook extends RemoteCollection<Contact> {
//private final static String TAG = "davdroid.CardDavAddressBook"; //private final static String TAG = "davdroid.CardDavAddressBook";
@ -20,8 +20,8 @@ public class CardDavAddressBook extends RemoteCollection<Contact> {
} }
@Override @Override
protected MultigetType multiGetType() { protected DavMultiget.Type multiGetType() {
return MultigetType.ADDRESS_BOOK; return DavMultiget.Type.ADDRESS_BOOK;
} }
@Override @Override

View File

@ -192,11 +192,11 @@ public class Event extends Resource {
if (exdate != null) if (exdate != null)
props.add(exdate); props.add(exdate);
if (summary != null) if (summary != null && !summary.isEmpty())
props.add(new Summary(summary)); props.add(new Summary(summary));
if (location != null) if (location != null && !location.isEmpty())
props.add(new Location(location)); props.add(new Location(location));
if (description != null) if (description != null && !description.isEmpty())
props.add(new Description(description)); props.add(new Description(description));
if (status != null) if (status != null)

View File

@ -10,7 +10,6 @@ package at.bitfire.davdroid.resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import net.fortuna.ical4j.model.ValidationException;
import android.accounts.Account; import android.accounts.Account;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
@ -72,8 +71,11 @@ public abstract class LocalCollection<T extends Resource> {
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() }, new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
where, null, null); where, null, null);
LinkedList<T> dirty = new LinkedList<T>(); LinkedList<T> dirty = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) while (cursor != null && cursor.moveToNext()) {
dirty.add(findById(cursor.getLong(0), true)); T resource = findById(cursor.getLong(0), true);
if (resource != null)
dirty.add(resource);
}
return dirty.toArray(new Resource[0]); return dirty.toArray(new Resource[0]);
} }
@ -85,8 +87,11 @@ public abstract class LocalCollection<T extends Resource> {
new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() }, new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() },
where, null, null); where, null, null);
LinkedList<T> deleted = new LinkedList<T>(); LinkedList<T> deleted = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) while (cursor != null && cursor.moveToNext()) {
deleted.add(findById(cursor.getLong(0), false)); T resource = findById(cursor.getLong(0), false);
if (resource != null)
deleted.add(resource);
}
return deleted.toArray(new Resource[0]); return deleted.toArray(new Resource[0]);
} }
@ -100,6 +105,7 @@ public abstract class LocalCollection<T extends Resource> {
LinkedList<T> fresh = new LinkedList<T>(); LinkedList<T> fresh = new LinkedList<T>();
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
T resource = findById(cursor.getLong(0), true); T resource = findById(cursor.getLong(0), true);
if (resource != null) {
resource.initialize(); resource.initialize();
// new record: set generated UID + remote file name in database // new record: set generated UID + remote file name in database
@ -111,6 +117,7 @@ public abstract class LocalCollection<T extends Resource> {
fresh.add(resource); fresh.add(resource);
} }
}
return fresh.toArray(new Resource[0]); return fresh.toArray(new Resource[0]);
} }
@ -162,9 +169,9 @@ public abstract class LocalCollection<T extends Resource> {
addDataRows(resource, -1, idx); addDataRows(resource, -1, idx);
} }
public void updateByRemoteName(Resource remoteResource) throws RemoteException, ValidationException { public void updateByRemoteName(Resource remoteResource) throws RemoteException {
T localResource = findByRemoteName(remoteResource.getName(), false); T localResource = findByRemoteName(remoteResource.getName(), false);
if (localResource != null) {
pendingOperations.add( pendingOperations.add(
buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource) buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource)
.withValue(entryColumnETag(), remoteResource.getETag()) .withValue(entryColumnETag(), remoteResource.getETag())
@ -174,6 +181,7 @@ public abstract class LocalCollection<T extends Resource> {
removeDataRows(localResource); removeDataRows(localResource);
addDataRows(remoteResource, localResource.getLocalID(), -1); addDataRows(remoteResource, localResource.getLocalID(), -1);
} }
}
public void delete(Resource resource) { public void delete(Resource resource) {
pendingOperations.add(ContentProviderOperation pendingOperations.add(ContentProviderOperation

View File

@ -14,19 +14,21 @@ import java.net.URISyntaxException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.ValidationException;
import org.apache.http.HttpException; import org.apache.http.HttpException;
import ezvcard.VCardException;
import android.util.Log; import android.util.Log;
import at.bitfire.davdroid.LoggingInputStream;
import at.bitfire.davdroid.webdav.DAVException;
import at.bitfire.davdroid.webdav.DavMultiget;
import at.bitfire.davdroid.webdav.HttpPropfind; import at.bitfire.davdroid.webdav.HttpPropfind;
import at.bitfire.davdroid.webdav.InvalidDavResponseException;
import at.bitfire.davdroid.webdav.WebDavResource; import at.bitfire.davdroid.webdav.WebDavResource;
import at.bitfire.davdroid.webdav.WebDavResource.MultigetType;
import at.bitfire.davdroid.webdav.WebDavResource.PutMode; import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
import ezvcard.VCardException;
public abstract class RemoteCollection<T extends Resource> { public abstract class RemoteCollection<T extends Resource> {
private static final String TAG = "davdroid.RemoteCollection"; private static final String TAG = "davdroid.RemoteCollection";
@ -34,7 +36,7 @@ public abstract class RemoteCollection<T extends Resource> {
@Getter WebDavResource collection; @Getter WebDavResource collection;
abstract protected String memberContentType(); abstract protected String memberContentType();
abstract protected MultigetType multiGetType(); abstract protected DavMultiget.Type multiGetType();
abstract protected T newResourceSkeleton(String name, String ETag); abstract protected T newResourceSkeleton(String name, String ETag);
public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException { public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
@ -48,13 +50,13 @@ public abstract class RemoteCollection<T extends Resource> {
try { try {
if (collection.getCTag() == null && collection.getMembers() == null) // not already fetched if (collection.getCTag() == null && collection.getMembers() == null) // not already fetched
collection.propfind(HttpPropfind.Mode.COLLECTION_CTAG); collection.propfind(HttpPropfind.Mode.COLLECTION_CTAG);
} catch (InvalidDavResponseException e) { } catch (DAVException e) {
return null; return null;
} }
return collection.getCTag(); return collection.getCTag();
} }
public Resource[] getMemberETags() throws IOException, InvalidDavResponseException, HttpException { public Resource[] getMemberETags() throws IOException, DAVException, HttpException {
collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG); collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG);
List<T> resources = new LinkedList<T>(); List<T> resources = new LinkedList<T>();
@ -67,7 +69,7 @@ public abstract class RemoteCollection<T extends Resource> {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Resource[] multiGet(Resource[] resources) throws IOException, InvalidDavResponseException, HttpException { public Resource[] multiGet(Resource[] resources) throws IOException, DAVException, HttpException {
try { try {
if (resources.length == 1) { if (resources.length == 1) {
Resource resource = get(resources[0]); Resource resource = get(resources[0]);
@ -78,7 +80,7 @@ public abstract class RemoteCollection<T extends Resource> {
for (Resource resource : resources) for (Resource resource : resources)
names.add(resource.getName()); names.add(resource.getName());
collection.multiGet(names.toArray(new String[0]), multiGetType()); collection.multiGet(multiGetType(), names.toArray(new String[0]));
LinkedList<T> foundResources = new LinkedList<T>(); LinkedList<T> foundResources = new LinkedList<T>();
if (collection.getMembers() != null) if (collection.getMembers() != null)
@ -116,7 +118,9 @@ public abstract class RemoteCollection<T extends Resource> {
public Resource get(Resource resources) throws IOException, HttpException, ParserException, VCardException { public Resource get(Resource resources) throws IOException, HttpException, ParserException, VCardException {
WebDavResource member = new WebDavResource(collection, resources.getName()); WebDavResource member = new WebDavResource(collection, resources.getName());
member.get(); member.get();
resources.parseEntity(member.getContent());
@Cleanup InputStream loggedContent = new LoggingInputStream(TAG, member.getContent());
resources.parseEntity(loggedContent);
return resources; return resources;
} }

View File

@ -23,7 +23,7 @@ import android.provider.Settings;
import android.util.Log; import android.util.Log;
import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.RemoteCollection; import at.bitfire.davdroid.resource.RemoteCollection;
import at.bitfire.davdroid.webdav.InvalidDavResponseException; import at.bitfire.davdroid.webdav.DAVException;
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter { public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
private final static String TAG = "davdroid.DavSyncAdapter"; private final static String TAG = "davdroid.DavSyncAdapter";
@ -72,7 +72,7 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
} catch (AuthenticationException ex) { } catch (AuthenticationException ex) {
syncResult.stats.numAuthExceptions++; syncResult.stats.numAuthExceptions++;
Log.e(TAG, "HTTP authorization error", ex); Log.e(TAG, "HTTP authorization error", ex);
} catch (InvalidDavResponseException ex) { } catch (DAVException ex) {
syncResult.stats.numParseExceptions++; syncResult.stats.numParseExceptions++;
Log.e(TAG, "Invalid DAV response", ex); Log.e(TAG, "Invalid DAV response", ex);
} catch (HttpException ex) { } catch (HttpException ex) {

View File

@ -148,15 +148,11 @@ public class SyncManager {
local.commit(); local.commit();
} }
Log.i(TAG, "Updating " + resourcesToUpdate.size() + " remote resource(s)"); Log.i(TAG, "Updating from " + resourcesToUpdate.size() + " remote resource(s)");
if (!resourcesToUpdate.isEmpty()) if (!resourcesToUpdate.isEmpty())
for (Resource res : dav.multiGet(resourcesToUpdate.toArray(new Resource[0]))) { for (Resource res : dav.multiGet(resourcesToUpdate.toArray(new Resource[0]))) {
Log.i(TAG, "Updating " + res.getName()); Log.i(TAG, "Updating " + res.getName());
try {
local.updateByRemoteName(res); local.updateByRemoteName(res);
} catch (ValidationException ex) {
Log.e(TAG, "Ignoring invalid remote resource: " + res.getName(), ex);
}
if (++syncResult.stats.numUpdates % MAX_UPDATES_BEFORE_COMMIT == 0) // avoid TransactionTooLargeException if (++syncResult.stats.numUpdates % MAX_UPDATES_BEFORE_COMMIT == 0) // avoid TransactionTooLargeException
local.commit(); local.commit();

View File

@ -0,0 +1,18 @@
package at.bitfire.davdroid.webdav;
import org.apache.http.HttpException;
public class DAVException extends HttpException {
private static final long serialVersionUID = -2118919144443165706L;
final private static String prefix = "Invalid DAV response: ";
public DAVException(String message) {
super(prefix + message);
}
public DAVException(String message, Throwable ex) {
super(prefix + message, ex);
}
}

View File

@ -0,0 +1,11 @@
package at.bitfire.davdroid.webdav;
public class DAVNoContentException extends DAVException {
private static final long serialVersionUID = 6256645020350945477L;
private final static String message = "HTTP response entity (content) expected but not received";
public DAVNoContentException() {
super(message);
}
}

View File

@ -0,0 +1,11 @@
package at.bitfire.davdroid.webdav;
public class DAVNoMultiStatusException extends DAVException {
private static final long serialVersionUID = -3600405724694229828L;
private final static String message = "207 Multi-Status expected but not received";
public DAVNoMultiStatusException() {
super(message);
}
}

View File

@ -7,6 +7,7 @@
******************************************************************************/ ******************************************************************************/
package at.bitfire.davdroid.webdav; package at.bitfire.davdroid.webdav;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.simpleframework.xml.Element; import org.simpleframework.xml.Element;
@ -15,9 +16,33 @@ import org.simpleframework.xml.Order;
@Order(elements={"prop","href"}) @Order(elements={"prop","href"})
public class DavMultiget { public class DavMultiget {
public enum Type {
ADDRESS_BOOK,
CALENDAR
}
@Element @Element
DavProp prop; DavProp prop;
@ElementList(inline=true) @ElementList(inline=true)
List<DavHref> hrefs; List<DavHref> hrefs;
public static DavMultiget newRequest(Type type, String names[]) {
DavMultiget multiget = (type == Type.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget();
multiget.prop = new DavProp();
multiget.prop.getetag = new DavProp.DavPropGetETag();
if (type == Type.ADDRESS_BOOK)
multiget.prop.addressData = new DavProp.DavPropAddressData();
else if (type == Type.CALENDAR)
multiget.prop.calendarData = new DavProp.DavPropCalendarData();
multiget.hrefs = new ArrayList<DavHref>(names.length);
for (String name : names)
multiget.hrefs.add(new DavHref(name));
return multiget;
}
} }

View File

@ -1,11 +0,0 @@
package at.bitfire.davdroid.webdav;
import org.apache.http.HttpException;
public class InvalidDavResponseException extends HttpException {
private static final long serialVersionUID = -2118919144443165706L;
public InvalidDavResponseException(String message) {
super("Invalid DAV response: " + message);
}
}

View File

@ -61,7 +61,7 @@ public class TlsSniSocketFactory implements LayeredSocketFactory {
// set up SNI before the handshake // set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i(TAG, "Setting SNI hostname"); Log.d(TAG, "Setting SNI hostname");
sslSocketFactory.setHostname(ssl, host); sslSocketFactory.setHostname(ssl, host);
} else } else
Log.i(TAG, "No SNI support below Android 4.2!"); Log.i(TAG, "No SNI support below Android 4.2!");

View File

@ -8,13 +8,11 @@
package at.bitfire.davdroid.webdav; package at.bitfire.davdroid.webdav;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -22,12 +20,13 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException; import org.apache.http.HttpException;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
@ -46,6 +45,7 @@ import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister; import org.simpleframework.xml.core.Persister;
import android.util.Log; import android.util.Log;
import at.bitfire.davdroid.LoggingInputStream;
import at.bitfire.davdroid.URIUtils; import at.bitfire.davdroid.URIUtils;
import at.bitfire.davdroid.resource.Event; import at.bitfire.davdroid.resource.Event;
import at.bitfire.davdroid.webdav.DavProp.DavPropComp; import at.bitfire.davdroid.webdav.DavProp.DavPropComp;
@ -64,10 +64,6 @@ public class WebDavResource {
CTAG, ETAG, CTAG, ETAG,
CONTENT_TYPE CONTENT_TYPE
} }
public enum MultigetType {
ADDRESS_BOOK,
CALENDAR
}
public enum PutMode { public enum PutMode {
ADD_DONT_OVERWRITE, ADD_DONT_OVERWRITE,
UPDATE_DONT_OVERWRITE UPDATE_DONT_OVERWRITE
@ -142,6 +138,8 @@ public class WebDavResource {
protected void checkResponse(StatusLine statusLine) throws HttpException { protected void checkResponse(StatusLine statusLine) throws HttpException {
int code = statusLine.getStatusCode(); int code = statusLine.getStatusCode();
Log.d(TAG, "Received " + statusLine.getProtocolVersion() + " " + code + " " + statusLine.getReasonPhrase());
if (code/100 == 1 || code/100 == 2) // everything OK if (code/100 == 1 || code/100 == 2) // everything OK
return; return;
@ -252,52 +250,38 @@ public class WebDavResource {
/* collection operations */ /* collection operations */
public void propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException { public void propfind(HttpPropfind.Mode mode) throws IOException, DAVException, HttpException {
HttpPropfind propfind = new HttpPropfind(location, mode); HttpPropfind propfind = new HttpPropfind(location, mode);
HttpResponse response = client.execute(propfind); HttpResponse response = client.execute(propfind);
checkResponse(response); checkResponse(response);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
InputStream content = response.getEntity().getContent(); throw new DAVNoMultiStatusException();
if (content == null)
throw new InvalidDavResponseException("Multistatus response without content");
// duplicate content for logging @Cleanup("consumeContent") HttpEntity entity = response.getEntity();
ByteArrayOutputStream logStream = new ByteArrayOutputStream(); if (entity == null)
InputStream is = new TeeInputStream(content, logStream); throw new DAVNoContentException();
InputStream content = entity.getContent();
if (content == null)
throw new DAVNoContentException();
@Cleanup LoggingInputStream loggedContent = new LoggingInputStream(TAG, content);
DavMultistatus multistatus; DavMultistatus multistatus;
try { try {
Serializer serializer = new Persister(); Serializer serializer = new Persister();
multistatus = serializer.read(DavMultistatus.class, is, false); multistatus = serializer.read(DavMultistatus.class, loggedContent, false);
} catch (Exception ex) { } catch (Exception ex) {
Log.w(TAG, "Invalid PROPFIND XML response", ex); throw new DAVException("Couldn't parse Multi-Status response on PROPFIND", ex);
throw new InvalidDavResponseException("Invalid PROPFIND response");
} finally {
Log.d(TAG, "Received multistatus response:\n" + logStream.toString("UTF-8"));
is.close();
content.close();
} }
processMultiStatus(multistatus); processMultiStatus(multistatus);
} else
throw new InvalidDavResponseException("Multistatus response expected");
} }
public void multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException { public void multiGet(DavMultiget.Type type, String[] names) throws IOException, DAVException, HttpException {
DavMultiget multiget = (type == MultigetType.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget(); List<String> hrefs = new LinkedList<String>();
multiget.prop = new DavProp();
multiget.prop.getetag = new DavProp.DavPropGetETag();
if (type == MultigetType.ADDRESS_BOOK)
multiget.prop.addressData = new DavProp.DavPropAddressData();
else if (type == MultigetType.CALENDAR)
multiget.prop.calendarData = new DavProp.DavPropCalendarData();
multiget.hrefs = new ArrayList<DavHref>(names.length);
for (String name : names) for (String name : names)
multiget.hrefs.add(new DavHref(location.resolve(name).getRawPath())); hrefs.add(location.resolve(name).getRawPath());
DavMultiget multiget = DavMultiget.newRequest(type, hrefs.toArray(new String[0]));
Serializer serializer = new Persister(); Serializer serializer = new Persister();
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
@ -305,38 +289,32 @@ public class WebDavResource {
serializer.write(multiget, writer); serializer.write(multiget, writer);
} catch (Exception ex) { } catch (Exception ex) {
Log.e(TAG, "Couldn't create XML multi-get request", ex); Log.e(TAG, "Couldn't create XML multi-get request", ex);
throw new InvalidDavResponseException("Couldn't create multi-get request"); throw new DAVException("Couldn't create multi-get request");
} }
HttpReport report = new HttpReport(location, writer.toString()); HttpReport report = new HttpReport(location, writer.toString());
HttpResponse response = client.execute(report); HttpResponse response = client.execute(report);
checkResponse(response); checkResponse(response);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
InputStream content = response.getEntity().getContent(); throw new DAVNoMultiStatusException();
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
if (entity == null)
throw new DAVNoContentException();
InputStream content = entity.getContent();
if (content == null) if (content == null)
throw new InvalidDavResponseException("Multistatus response without content"); throw new DAVNoContentException();
DavMultistatus multistatus; @Cleanup LoggingInputStream loggedContent = new LoggingInputStream(TAG, content);
// duplicate content for logging
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
InputStream is = new TeeInputStream(content, logStream, true);
DavMultistatus multiStatus;
try { try {
multistatus = serializer.read(DavMultistatus.class, is, false); multiStatus = serializer.read(DavMultistatus.class, loggedContent, false);
} catch (Exception ex) { } catch (Exception ex) {
Log.e(TAG, "Couldn't parse multi-get response", ex); throw new DAVException("Couldn't parse Multi-Status response on REPORT multi-get", ex);
throw new InvalidDavResponseException("Invalid multi-get response");
} finally {
Log.d(TAG, "Received multistatus response:\n" + logStream.toString("UTF-8"));
is.close();
content.close();
} }
processMultiStatus(multistatus); processMultiStatus(multiStatus);
} else
throw new InvalidDavResponseException("Multistatus response expected");
} }
@ -473,4 +451,5 @@ public class WebDavResource {
this.members = members; this.members = members;
} }
} }

View File

@ -13,7 +13,11 @@ public class LombokTest extends TestCase {
} }
public void testNonNull() { public void testNonNull() {
assertNull(appendSlash(null)); try {
//assertEquals("1/", appendSlash("1")); appendSlash(null);
fail();
} catch(NullPointerException e) {
}
assertEquals("1/", appendSlash("1"));
} }
} }

View File

@ -10,12 +10,12 @@ import org.apache.http.HttpException;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.webdav.DAVException;
import at.bitfire.davdroid.webdav.DavMultiget;
import at.bitfire.davdroid.webdav.HttpPropfind; import at.bitfire.davdroid.webdav.HttpPropfind;
import at.bitfire.davdroid.webdav.InvalidDavResponseException;
import at.bitfire.davdroid.webdav.NotFoundException; import at.bitfire.davdroid.webdav.NotFoundException;
import at.bitfire.davdroid.webdav.PreconditionFailedException; import at.bitfire.davdroid.webdav.PreconditionFailedException;
import at.bitfire.davdroid.webdav.WebDavResource; import at.bitfire.davdroid.webdav.WebDavResource;
import at.bitfire.davdroid.webdav.WebDavResource.MultigetType;
import at.bitfire.davdroid.webdav.WebDavResource.PutMode; import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
// tests require running robohydra! // tests require running robohydra!
@ -88,7 +88,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
try { try {
simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
fail(); fail();
} catch(InvalidDavResponseException ex) { } catch(DAVException ex) {
} }
assertNull(simpleFile.getCurrentUserPrincipal()); assertNull(simpleFile.getCurrentUserPrincipal());
} }
@ -147,9 +147,9 @@ public class WebDavResourceTest extends InstrumentationTestCase {
)); ));
} }
public void testMultiGet() throws InvalidDavResponseException, IOException, HttpException { public void testMultiGet() throws DAVException, IOException, HttpException {
WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf", true); WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf", true);
davAddressBook.multiGet(new String[] { "1.vcf", "2.vcf" }, MultigetType.ADDRESS_BOOK); davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[] { "1.vcf", "2.vcf" });
assertEquals(2, davAddressBook.getMembers().size()); assertEquals(2, davAddressBook.getMembers().size());
for (WebDavResource member : davAddressBook.getMembers()) { for (WebDavResource member : davAddressBook.getMembers()) {
assertNotNull(member.getContent()); assertNotNull(member.getContent());