Skip to content

Commit a009be9

Browse files
authored
Merge pull request #3078 from linuxhenhao/fix/webdav-http-409-file-transaction-error
Fix WebDAV HTTP 409 error with file transactions
2 parents dfca85c + bcf3408 commit a009be9

1 file changed

Lines changed: 110 additions & 4 deletions

File tree

  • src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage

src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/WebDavStorage.java

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,19 @@ public void renameOrMoveWebDavResource(String sourcePath, String destinationPath
215215
.method("MOVE", null) // "MOVE" is the HTTP method
216216
.header("Destination", destinationCi.URL); // New URI for the resource
217217

218-
// Add Overwrite header
218+
// Use delete-then-move strategy to avoid HTTP 409 conflicts
219+
if (overwrite) {
220+
try {
221+
// Try to delete the destination file first if it exists
222+
deleteFileIfExists(destinationCi);
223+
} catch (Exception e) {
224+
// Ignore deletion errors - the file might not exist or we might not have permission
225+
// The MOVE operation will fail if the destination can't be overwritten
226+
Log.d("WebDavStorage", "Failed to delete destination file before move (this may be normal): " + e.getMessage());
227+
}
228+
}
229+
230+
// Add Overwrite header (but don't rely on it solely)
219231
if (overwrite) {
220232
requestBuilder.header("Overwrite", "T"); // 'T' for true
221233
} else {
@@ -235,7 +247,102 @@ public void renameOrMoveWebDavResource(String sourcePath, String destinationPath
235247
}
236248
else
237249
{
238-
throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message());
250+
int statusCode = response.code();
251+
String errorMessage = "Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + statusCode + " " + response.message();
252+
253+
// If we get a 409 conflict and overwrite is true, try retry with enhanced cleanup
254+
if (overwrite && statusCode == 409) {
255+
try {
256+
response.close();
257+
// Force delete destination and retry
258+
deleteFileIfExists(destinationCi);
259+
// Small delay to ensure server processes the deletion
260+
Thread.sleep(100);
261+
262+
// Retry the MOVE operation
263+
Response retryResponse = getClient(sourceCi).newCall(request).execute();
264+
if (retryResponse.isSuccessful()) {
265+
retryResponse.close();
266+
return; // Success on retry
267+
} else {
268+
errorMessage = "Rename/Move failed even after retry for " + sourceCi.URL + " to " + destinationCi.URL + ": " + retryResponse.code() + " " + retryResponse.message();
269+
retryResponse.close();
270+
}
271+
} catch (Exception retryException) {
272+
errorMessage = "Rename/Move failed and retry attempt also failed: " + errorMessage + " (Retry error: " + retryException.getMessage() + ")";
273+
}
274+
}
275+
276+
throw new Exception(errorMessage);
277+
}
278+
}
279+
280+
/**
281+
* Helper method to delete a file if it exists
282+
* Uses PROPFIND to check existence first to avoid errors on non-existent files
283+
*/
284+
private void deleteFileIfExists(ConnectionInfo ci) throws Exception {
285+
try {
286+
// First check if file exists using PROPFIND
287+
if (fileExists(ci)) {
288+
// File exists, proceed with deletion
289+
Request request = new Request.Builder()
290+
.url(new URL(ci.URL))
291+
.delete()
292+
.build();
293+
Response response = getClient(ci).newCall(request).execute();
294+
try {
295+
// Accept 200 OK, 204 No Content, or 404 Not Found (already deleted)
296+
if (!response.isSuccessful() && response.code() != 404) {
297+
throw new Exception("Delete failed with status: " + response.code() + " " + response.message());
298+
}
299+
} finally {
300+
response.close();
301+
}
302+
}
303+
} catch (FileNotFoundException e) {
304+
// File doesn't exist, which is fine
305+
Log.d("WebDavStorage", "File does not exist, no deletion needed: " + ci.URL);
306+
}
307+
}
308+
309+
/**
310+
* Helper method to check if a file exists using PROPFIND
311+
*/
312+
private boolean fileExists(ConnectionInfo ci) throws Exception {
313+
try {
314+
Request request = new Request.Builder()
315+
.url(new URL(ci.URL))
316+
.method("PROPFIND", RequestBody.create(MediaType.parse("application/xml"),
317+
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
318+
"<D:propfind xmlns:D=\"DAV:\">\n" +
319+
" <D:prop>\n" +
320+
" <D:resourcetype/>\n" +
321+
" </D:prop>\n" +
322+
"</D:propfind>"))
323+
.header("Depth", "0")
324+
.header("Content-Type", "application/xml")
325+
.build();
326+
327+
Response response = getClient(ci).newCall(request).execute();
328+
try {
329+
// 200 OK means file exists, 404 means it doesn't exist
330+
if (response.isSuccessful()) {
331+
return true;
332+
} else if (response.code() == 404) {
333+
return false;
334+
} else {
335+
// For other status codes, assume file exists to be safe
336+
Log.w("WebDavStorage", "Unexpected status checking file existence: " + response.code() + " for " + ci.URL);
337+
return true;
338+
}
339+
} finally {
340+
response.close();
341+
}
342+
} catch (Exception e) {
343+
// If PROPFIND fails, assume file exists to be safe
344+
Log.w("WebDavStorage", "Error checking file existence, assuming it exists: " + e.getMessage());
345+
return true;
239346
}
240347
}
241348

@@ -307,7 +414,7 @@ public long contentLength() {
307414
else
308415
{
309416
requestBody = RequestBody.create(data, MediaType.parse("application/binary"));
310-
}
417+
}
311418

312419
Request request = new Request.Builder()
313420
.url(new URL(ci.URL))
@@ -645,4 +752,3 @@ public void prepareFileUsage(Context appContext, String path) {
645752
}
646753

647754
}
648-

0 commit comments

Comments
 (0)