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:
parent
ec4fedf04e
commit
6cfaad35b1
@ -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"
|
||||||
|
58
src/at/bitfire/davdroid/LoggingInputStream.java
Normal file
58
src/at/bitfire/davdroid/LoggingInputStream.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
18
src/at/bitfire/davdroid/webdav/DAVException.java
Normal file
18
src/at/bitfire/davdroid/webdav/DAVException.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
11
src/at/bitfire/davdroid/webdav/DAVNoContentException.java
Normal file
11
src/at/bitfire/davdroid/webdav/DAVNoContentException.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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!");
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user