From ad733ebff138a598b6fd2487f9bbc1724f475656 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 7 Jul 2015 00:25:49 +0200 Subject: [PATCH] Handle 409 Conflict status codes (fixes #563) --- .../davdroid/webdav/WebDavResourceTest.java | 14 ++++++++++++-- .../androidTest/robohydra/plugins/dav/index.js | 3 +++ .../bitfire/davdroid/resource/ServerInfo.java | 3 --- .../davdroid/syncadapter/SyncManager.java | 5 +++-- .../ui/setup/SelectCollectionsAdapter.java | 1 - .../ui/setup/SelectCollectionsFragment.java | 1 - .../davdroid/webdav/ConflictException.java | 17 +++++++++++++++++ .../bitfire/davdroid/webdav/WebDavResource.java | 2 ++ build.gradle | 2 +- lib/httpclient-android/build.gradle | 2 +- 10 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/webdav/ConflictException.java diff --git a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java b/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java index 0d4fcde0..1205692d 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java +++ b/app/src/androidTest/java/at/bitfire/davdroid/webdav/WebDavResourceTest.java @@ -13,8 +13,10 @@ import android.test.InstrumentationTestCase; import org.apache.commons.io.IOUtils; import org.apache.http.impl.client.CloseableHttpClient; +import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.util.Arrays; import javax.net.ssl.SSLPeerUnverifiedException; @@ -226,16 +228,24 @@ public class WebDavResourceTest extends InstrumentationTestCase { } } - public void testPutUpdateDontOverwrite() throws Exception { + public void testPutUpdateDontOverwrite() throws Exception { // should succeed on an existing file assertEquals("has-just-been-updated", davExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE)); - // should fail on a non-existing file + // should fail on a non-existing file (resource has been deleted on server, thus server returns 412) try { davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); fail(); } catch(PreconditionFailedException ex) { } + + // should fail on existing file with wrong ETag (resource has changed on server, thus server returns 409) + try { + WebDavResource dav = new WebDavResource(davCollection, new URI("collection/existing.file?conflict=1")); + dav.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); + fail(); + } catch(ConflictException ex) { + } } public void testDelete() throws Exception { diff --git a/app/src/androidTest/robohydra/plugins/dav/index.js b/app/src/androidTest/robohydra/plugins/dav/index.js index 9719ab5f..476b4e51 100644 --- a/app/src/androidTest/robohydra/plugins/dav/index.js +++ b/app/src/androidTest/robohydra/plugins/dav/index.js @@ -245,6 +245,9 @@ exports.getBodyParts = function(conf) { if (req.method == "PUT") { if (req.headers['if-none-match']) /* requested "don't overwrite", but this file exists */ res.statusCode = 412; + else if (req.headers['if-match'] && req.queryParams && req.queryParams.conflict) + /* requested "don't overwrite", but this file exists with newer content */ + res.statusCode = 409; else { res.statusCode = 204; res.headers["ETag"] = "has-just-been-updated"; diff --git a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java index 63116ea4..6ea6b2b9 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java @@ -7,9 +7,6 @@ */ package at.bitfire.davdroid.resource; -import android.os.Parcel; -import android.os.Parcelable; - import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java index e5da1c71..9453bded 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -21,6 +21,7 @@ import at.bitfire.davdroid.resource.LocalStorageException; import at.bitfire.davdroid.resource.RecordNotFoundException; import at.bitfire.davdroid.resource.RemoteCollection; import at.bitfire.davdroid.resource.Resource; +import at.bitfire.davdroid.webdav.ConflictException; import at.bitfire.davdroid.webdav.DavException; import at.bitfire.davdroid.webdav.HttpException; import at.bitfire.davdroid.webdav.NotFoundException; @@ -141,7 +142,7 @@ public class SyncManager { local.updateETag(res, eTag); local.clearDirty(res); count++; - } catch (PreconditionFailedException e) { + } catch (PreconditionFailedException|ConflictException e) { Log.i(TAG, "Didn't overwrite existing resource with other content"); } catch (RecordNotFoundException e) { Log.wtf(TAG, "Couldn't read new record", e); @@ -165,7 +166,7 @@ public class SyncManager { local.updateETag(res, eTag); local.clearDirty(res); count++; - } catch (PreconditionFailedException e) { + } catch (PreconditionFailedException|ConflictException e) { Log.i(TAG, "Locally changed resource has been changed on the server in the meanwhile"); } catch (RecordNotFoundException e) { Log.e(TAG, "Couldn't read dirty record", e); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java index 7aeb2314..54dfa240 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java @@ -10,7 +10,6 @@ package at.bitfire.davdroid.ui.setup; import android.annotation.SuppressLint; import android.content.Context; import android.text.Html; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java index 4fcdd25c..8f9631a6 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java @@ -9,7 +9,6 @@ package at.bitfire.davdroid.ui.setup; import android.app.ListFragment; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/ConflictException.java b/app/src/main/java/at/bitfire/davdroid/webdav/ConflictException.java new file mode 100644 index 00000000..2ce29336 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/webdav/ConflictException.java @@ -0,0 +1,17 @@ +/* + * 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.webdav; + +import org.apache.http.HttpStatus; + +public class ConflictException extends HttpException { + public ConflictException(String reason) { + super(HttpStatus.SC_CONFLICT, reason); + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java b/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java index 7e8a71ac..bf3b2a17 100644 --- a/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java +++ b/app/src/main/java/at/bitfire/davdroid/webdav/WebDavResource.java @@ -426,6 +426,8 @@ public class WebDavResource { throw new NotAuthorizedException(reason); case HttpStatus.SC_NOT_FOUND: throw new NotFoundException(reason); + case HttpStatus.SC_CONFLICT: + throw new ConflictException(reason); case HttpStatus.SC_PRECONDITION_FAILED: throw new PreconditionFailedException(reason); default: diff --git a/build.gradle b/build.gradle index 7c3f7687..eaab37ef 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:1.0.1' } } diff --git a/lib/httpclient-android/build.gradle b/lib/httpclient-android/build.gradle index d55e82bf..032ddfc5 100644 --- a/lib/httpclient-android/build.gradle +++ b/lib/httpclient-android/build.gradle @@ -30,7 +30,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.android.tools.build:gradle:1.0.1' } }