diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 3c9e98b4e..2b4342e0c 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -8637,6 +8637,14 @@
+
+
+
+
+
+
+
+
diff --git a/library/build.gradle b/library/build.gradle
index 5512aa552..54df040b3 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -64,7 +64,7 @@ dependencies {
implementation 'org.apache.jackrabbit:jackrabbit-webdav:2.13.5'
api 'com.squareup.okhttp3:okhttp:5.5.0'
- implementation 'com.github.bitfireAT:dav4jvm:3.0.2'
+ implementation 'com.github.bitfireAT:dav4jvm:2.1.3'
// in transition phase, we use old and new libs
implementation group: 'com.google.code.gson', name: 'gson', version: '2.14.0'
implementation 'androidx.annotation:annotation:1.10.0'
diff --git a/library/src/androidTest/java/com/nextcloud/android/lib/resources/directediting/DirectEditingOpenFileRemoteOperationIT.java b/library/src/androidTest/java/com/nextcloud/android/lib/resources/directediting/DirectEditingOpenFileRemoteOperationIT.java
index 9f23bf3a7..f4680c072 100644
--- a/library/src/androidTest/java/com/nextcloud/android/lib/resources/directediting/DirectEditingOpenFileRemoteOperationIT.java
+++ b/library/src/androidTest/java/com/nextcloud/android/lib/resources/directediting/DirectEditingOpenFileRemoteOperationIT.java
@@ -7,6 +7,9 @@
*/
package com.nextcloud.android.lib.resources.directediting;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import com.owncloud.android.AbstractIT;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
@@ -18,9 +21,6 @@
import java.io.IOException;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
public class DirectEditingOpenFileRemoteOperationIT extends AbstractIT {
@Test
public void openFile() throws IOException {
@@ -34,7 +34,7 @@ public void openFile() throws IOException {
1464818400
).execute(client).isSuccess());
- TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(client).isSuccess());
+ TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(nextcloudClient).isSuccess());
// open file
RemoteOperationResult result = new DirectEditingOpenFileRemoteOperation(remotePath, "text")
@@ -58,7 +58,7 @@ public void openFileWithSpecialChars() throws IOException {
1464818400
).execute(client).isSuccess());
- TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(client).isSuccess());
+ TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(nextcloudClient).isSuccess());
// open file
RemoteOperationResult result = new DirectEditingOpenFileRemoteOperation(remotePath, "text")
@@ -82,7 +82,7 @@ public void openFileWithSpecialChars2() throws IOException {
1464818400
).execute(client).isSuccess());
- TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(client).isSuccess());
+ TestCase.assertTrue(new ReadFileRemoteOperation(remotePath).execute(nextcloudClient).isSuccess());
// open file
RemoteOperationResult result = new DirectEditingOpenFileRemoteOperation(remotePath, "text")
@@ -98,7 +98,7 @@ public void openFileWithSpecialChars2() throws IOException {
public void openNonExistingFile() {
String remotePath = "/nonExisting.md";
- TestCase.assertFalse(new ReadFileRemoteOperation(remotePath).execute(client).isSuccess());
+ TestCase.assertFalse(new ReadFileRemoteOperation(remotePath).execute(nextcloudClient).isSuccess());
// open file
RemoteOperationResult result = new DirectEditingOpenFileRemoteOperation(remotePath, "text")
diff --git a/library/src/androidTest/java/com/nextcloud/android/lib/resources/files/ToggleFileLockRemoteOperationIT.kt b/library/src/androidTest/java/com/nextcloud/android/lib/resources/files/ToggleFileLockRemoteOperationIT.kt
index ec029da67..0898f1ee3 100644
--- a/library/src/androidTest/java/com/nextcloud/android/lib/resources/files/ToggleFileLockRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/nextcloud/android/lib/resources/files/ToggleFileLockRemoteOperationIT.kt
@@ -11,7 +11,6 @@ import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation
import com.owncloud.android.lib.resources.files.model.FileLockType
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.status.NextcloudVersion.Companion.nextcloud_24
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -34,20 +33,20 @@ class ToggleFileLockRemoteOperationIT : AbstractIT() {
.isSuccess
)
val initialFile =
- ReadFileRemoteOperation(remotePath).execute(client).singleData as RemoteFile
+ ReadFileRemoteOperation(remotePath).execute(nextcloudClient).resultData
assertFalse("File shouldn't be locked", initialFile.isLocked)
// lock file
val lockResult = ToggleFileLockRemoteOperation(toLock = true, remotePath).execute(nextcloudClient)
assertTrue("File lock failed", lockResult.isSuccess)
- val lockFile = ReadFileRemoteOperation(remotePath).execute(client).singleData as RemoteFile
+ val lockFile = ReadFileRemoteOperation(remotePath).execute(nextcloudClient).resultData
assertTrue("File should be locked", lockFile.isLocked)
assertEquals("Wrong lock type", FileLockType.MANUAL, lockFile.lockType)
// unlock again
val unlockResult = ToggleFileLockRemoteOperation(toLock = false, remotePath).execute(nextcloudClient)
assertTrue("File unlock failed", unlockResult.isSuccess)
- val unlockFile = ReadFileRemoteOperation(remotePath).execute(client).singleData as RemoteFile
+ val unlockFile = ReadFileRemoteOperation(remotePath).execute(nextcloudClient).resultData
assertFalse("File shouldn't be locked", unlockFile.isLocked)
}
}
diff --git a/library/src/androidTest/java/com/nextcloud/android/lib/resources/search/UnifiedSearchRemoteOperationIT.kt b/library/src/androidTest/java/com/nextcloud/android/lib/resources/search/UnifiedSearchRemoteOperationIT.kt
index 17962eed6..9fc44f763 100644
--- a/library/src/androidTest/java/com/nextcloud/android/lib/resources/search/UnifiedSearchRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/nextcloud/android/lib/resources/search/UnifiedSearchRemoteOperationIT.kt
@@ -10,7 +10,6 @@ package com.nextcloud.android.lib.resources.search
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.status.NextcloudVersion
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -48,8 +47,8 @@ class UnifiedSearchRemoteOperationIT : AbstractIT() {
assertTrue(CreateFolderRemoteOperation(remotePath, true).execute(client).isSuccess)
val remoteFile =
ReadFileRemoteOperation(remotePath)
- .execute(client)
- .data[0] as RemoteFile
+ .execute(nextcloudClient)
+ .resultData
val fileId = remoteFile.localId
val result = UnifiedSearchRemoteOperation("files", "test").execute(nextcloudClient)
diff --git a/library/src/androidTest/java/com/owncloud/android/AbstractIT.java b/library/src/androidTest/java/com/owncloud/android/AbstractIT.java
index 59a163689..4aa0cb1b1 100644
--- a/library/src/androidTest/java/com/owncloud/android/AbstractIT.java
+++ b/library/src/androidTest/java/com/owncloud/android/AbstractIT.java
@@ -94,6 +94,10 @@ public static void beforeAll() throws InterruptedException,
String loginName = arguments.getString("TEST_SERVER_USERNAME");
String password = arguments.getString("TEST_SERVER_PASSWORD");
+ // E2E server app checks for official NC client with >=3.13.0,
+ // and blocks all other clients, e.g. 3rd party apps using this lib
+ OwnCloudClientManagerFactory.setUserAgent("Mozilla/5.0 (Android) Nextcloud-android/3.13.0");
+
client = OwnCloudClientFactory.createOwnCloudClient(url, context, true);
client.setCredentials(new OwnCloudBasicCredentials(loginName, password));
client.setUserId(loginName); // for test same as userId
@@ -106,8 +110,6 @@ public static void beforeAll() throws InterruptedException,
client2.setCredentials(new OwnCloudBasicCredentials(loginName2, password2));
client2.setUserId(loginName2); // for test same as userId
- OwnCloudClientManagerFactory.setUserAgent("Mozilla/5.0 (Android) Nextcloud-android/1.0.0");
-
String userId = loginName; // for test same as userId
String credentials = Credentials.basic(loginName, password);
nextcloudClient = new NextcloudClient(url, userId, credentials, context);
diff --git a/library/src/androidTest/java/com/owncloud/android/CreateFolderRemoteOperationIT.java b/library/src/androidTest/java/com/owncloud/android/CreateFolderRemoteOperationIT.java
index 4f7b02dfc..9740eea68 100644
--- a/library/src/androidTest/java/com/owncloud/android/CreateFolderRemoteOperationIT.java
+++ b/library/src/androidTest/java/com/owncloud/android/CreateFolderRemoteOperationIT.java
@@ -92,10 +92,10 @@ public void testFileID() {
RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
assertTrue(result.isSuccess());
- RemoteOperationResult readResult = new ReadFileRemoteOperation(remotePath).execute(client);
+ RemoteOperationResult readResult = new ReadFileRemoteOperation(remotePath).execute(nextcloudClient);
assertTrue(readResult.isSuccess());
- String remoteId = ((RemoteFile) readResult.getData().get(0)).getRemoteId();
+ String remoteId = readResult.getResultData().getRemoteId();
assertEquals(result.getResultData(), remoteId);
}
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt
index f344633eb..947aa39bc 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt
@@ -10,7 +10,6 @@ package com.owncloud.android.lib.resources.comments
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import junit.framework.Assert.assertTrue
import org.junit.Test
@@ -25,8 +24,8 @@ class CommentFileRemoteOperationIT : AbstractIT() {
.isSuccess
)
- val readResult = ReadFileRemoteOperation(remotePath).execute(client)
- val remoteFile = readResult.data.get(0) as RemoteFile
+ val readResult = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
+ val remoteFile = readResult.resultData
assertTrue(
CommentFileRemoteOperation("test", remoteFile.localId)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/UpdateMetadataRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/UpdateMetadataRemoteOperationIT.kt
index 855a52bc1..8b0f2271c 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/UpdateMetadataRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/UpdateMetadataRemoteOperationIT.kt
@@ -14,7 +14,6 @@ import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import junit.framework.TestCase
import org.junit.Assert
@@ -33,8 +32,7 @@ class UpdateMetadataRemoteOperationIT : AbstractIT() {
// create folder
val folder = "/" + make(20) + "/"
TestCase.assertTrue(CreateFolderRemoteOperation(folder, true).execute(client).isSuccess)
- val remoteFolder =
- ReadFileRemoteOperation(folder).execute(client).getSingleData() as RemoteFile?
+ val remoteFolder = ReadFileRemoteOperation(folder).execute(nextcloudClient).resultData
TestCase.assertNotNull(remoteFolder)
@@ -117,8 +115,7 @@ class UpdateMetadataRemoteOperationIT : AbstractIT() {
// create folder
val folder = "/" + make(20) + "/"
TestCase.assertTrue(CreateFolderRemoteOperation(folder, true).execute(client).isSuccess)
- val remoteFolder =
- ReadFileRemoteOperation(folder).execute(client).getSingleData() as RemoteFile?
+ val remoteFolder = ReadFileRemoteOperation(folder).execute(nextcloudClient).resultData
TestCase.assertNotNull(remoteFolder)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperationIT.kt
index 44123907c..155fbb8e0 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperationIT.kt
@@ -9,7 +9,6 @@ package com.owncloud.android.lib.resources.files
import com.owncloud.android.AbstractIT
import com.owncloud.android.lib.common.operations.RemoteOperationResult
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -26,8 +25,7 @@ class CheckEtagRemoteOperationIT : AbstractIT() {
.isSuccess
)
- val readResult = ReadFileRemoteOperation(remotePath).execute(client)
- val remoteFile = readResult.data[0] as RemoteFile
+ val remoteFile = ReadFileRemoteOperation(remotePath).execute(nextcloudClient).resultData
val eTag = remoteFile.etag
var eTagResult = CheckEtagRemoteOperation(remotePath, eTag).execute(client)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt
index 891c9da29..bede2b35a 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperationIT.kt
@@ -8,7 +8,6 @@
package com.owncloud.android.lib.resources.files
import com.owncloud.android.AbstractIT
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation
import com.owncloud.android.lib.resources.files.model.GeoLocation
import com.owncloud.android.lib.resources.files.model.ImageDimension
@@ -23,14 +22,24 @@ import org.junit.Test
class ReadFileRemoteOperationIT : AbstractIT() {
@Test
fun readRemoteFolder() {
- val remotePath = "/test/"
+ val remotePath = "/folder/"
assertTrue(CreateFolderRemoteOperation(remotePath, true).execute(client).isSuccess)
- val result = ReadFileRemoteOperation(remotePath).execute(client)
+ // use ownCloud client for reference
+ var result = ReadFileRemoteOperation(remotePath).execute(client)
+ assertTrue(result.isSuccess)
+ val ocClientFile = result.resultData
+ assertEquals(remotePath, ocClientFile.remotePath)
+ result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
assertTrue(result.isSuccess)
- assertEquals(remotePath, (result.data[0] as RemoteFile).remotePath)
+ val ncClientFile = result.resultData
+ assertEquals(remotePath, result.resultData.remotePath)
+
+ assertEquals(ocClientFile.modifiedTimestamp, ncClientFile.modifiedTimestamp)
+
+ assertTrue(remoteFilesEqual(ocClientFile, ncClientFile))
}
@Test
@@ -68,13 +77,13 @@ class ReadFileRemoteOperationIT : AbstractIT() {
).execute(client).isSuccess
)
- val movieFileResult = ReadFileRemoteOperation(movieFilePath).execute(client)
+ val movieFileResult = ReadFileRemoteOperation(movieFilePath).execute(nextcloudClient)
assertTrue(movieFileResult.isSuccess)
- val movieRemoteFile = movieFileResult.data[0] as RemoteFile
+ val movieRemoteFile = movieFileResult.resultData
- val livePhotoResult = ReadFileRemoteOperation(livePhotoPath).execute(client)
+ val livePhotoResult = ReadFileRemoteOperation(livePhotoPath).execute(nextcloudClient)
assertTrue(livePhotoResult.isSuccess)
- val livePhotoRemoteFile = livePhotoResult.data[0] as RemoteFile
+ val livePhotoRemoteFile = livePhotoResult.resultData
assertEquals(livePhotoRemoteFile.livePhoto, movieRemoteFile.remotePath)
assertTrue(movieRemoteFile.hidden)
@@ -91,10 +100,18 @@ class ReadFileRemoteOperationIT : AbstractIT() {
.isSuccess
)
- val result = ReadFileRemoteOperation(remotePath).execute(client)
+ // use ownCloud client for reference
+ var result = ReadFileRemoteOperation(remotePath).execute(client)
+ assertTrue(result.isSuccess)
+ val ocClientFile = result.resultData
+ assertEquals(remotePath, ocClientFile.remotePath)
+ result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
assertTrue(result.isSuccess)
- assertEquals(remotePath, (result.data[0] as RemoteFile).remotePath)
+ val ncClientFile = result.resultData
+ assertEquals(remotePath, result.resultData.remotePath)
+
+ assertTrue(remoteFilesEqual(ocClientFile, ncClientFile))
}
@Test
@@ -108,10 +125,10 @@ class ReadFileRemoteOperationIT : AbstractIT() {
.isSuccess
)
- val result = ReadFileRemoteOperation(remotePath).execute(client)
+ val result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
assertTrue(result.isSuccess)
- val remoteFile = result.data[0] as RemoteFile
+ val remoteFile = result.resultData
@Suppress("Detekt.MagicNumber")
if (isServerAtLeast(NextcloudVersion.nextcloud_23)) {
@@ -134,15 +151,10 @@ class ReadFileRemoteOperationIT : AbstractIT() {
@Test
fun readEncryptedState() {
val remotePath = "/testEncryptedFolder/"
-
- // E2E server app checks for official NC client with >=3.13.0,
- // and blocks all other clients, e.g. 3rd party apps using this lib
- OwnCloudClientManagerFactory.setUserAgent("Mozilla/5.0 (Android) Nextcloud-android/3.13.0")
-
assertTrue(CreateFolderRemoteOperation(remotePath, true).execute(client).isSuccess)
- var result = ReadFileRemoteOperation(remotePath).execute(client)
- val remoteFile = result.data[0] as RemoteFile
+ var result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
+ val remoteFile = result.resultData
assertTrue(result.isSuccess)
assertFalse(remoteFile.isEncrypted)
@@ -159,7 +171,47 @@ class ReadFileRemoteOperationIT : AbstractIT() {
)
// re-read
- result = ReadFileRemoteOperation(remotePath).execute(client)
- assertEquals(true, (result.data[0] as RemoteFile).isEncrypted)
+ result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
+ assertEquals(true, result.resultData.isEncrypted)
}
+
+ private fun remoteFilesEqual(
+ a: RemoteFile,
+ b: RemoteFile
+ ): Boolean =
+ a.remotePath == b.remotePath &&
+ a.mimeType == b.mimeType &&
+ a.length == b.length &&
+ a.creationTimestamp == b.creationTimestamp &&
+ a.modifiedTimestamp == b.modifiedTimestamp &&
+ a.uploadTimestamp == b.uploadTimestamp &&
+ a.etag == b.etag &&
+ a.permissions == b.permissions &&
+ a.localId == b.localId &&
+ a.remoteId == b.remoteId &&
+ a.size == b.size &&
+ a.isFavorite == b.isFavorite &&
+ a.isEncrypted == b.isEncrypted &&
+ a.mountType == b.mountType &&
+ a.ownerId == b.ownerId &&
+ a.ownerDisplayName == b.ownerDisplayName &&
+ a.unreadCommentsCount == b.unreadCommentsCount &&
+ a.isHasPreview == b.isHasPreview &&
+ a.note == b.note &&
+ a.sharees.contentEquals(b.sharees) &&
+ a.richWorkspace == b.richWorkspace &&
+ a.isLocked == b.isLocked &&
+ a.lockType == b.lockType &&
+ a.lockOwner == b.lockOwner &&
+ a.lockOwnerDisplayName == b.lockOwnerDisplayName &&
+ a.lockTimestamp == b.lockTimestamp &&
+ a.lockOwnerEditor == b.lockOwnerEditor &&
+ a.lockTimeout == b.lockTimeout &&
+ a.lockToken == b.lockToken &&
+ a.tags.contentEquals(b.tags) &&
+ a.imageDimension == b.imageDimension &&
+ a.geoLocation == b.geoLocation &&
+ a.hidden == b.hidden &&
+ a.livePhoto == b.livePhoto &&
+ a.fileDownloadLimit == b.fileDownloadLimit
}
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperationIT.kt
index 816112e11..2438e658f 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperationIT.kt
@@ -8,7 +8,6 @@
package com.owncloud.android.lib.resources.files
import com.owncloud.android.AbstractIT
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.lib.resources.status.GetCapabilitiesRemoteOperation
import com.owncloud.android.lib.resources.status.NextcloudVersion
import org.junit.Assert.assertEquals
@@ -33,7 +32,7 @@ class ReadFileVersionsRemoteOperationIT : AbstractIT() {
assertTrue("Error uploading file $filePath: $uploadResult", uploadResult.isSuccess)
- var remoteFile = ReadFileRemoteOperation(filePath).execute(client).data[0] as RemoteFile
+ var remoteFile = ReadFileRemoteOperation(filePath).execute(nextcloudClient).resultData
var sutResult = ReadFileVersionsRemoteOperation(remoteFile.localId).execute(client)
@@ -64,7 +63,7 @@ class ReadFileVersionsRemoteOperationIT : AbstractIT() {
assertTrue("Error uploading file $filePath: $uploadResult", uploadResult.isSuccess)
- remoteFile = ReadFileRemoteOperation(filePath).execute(client).data[0] as RemoteFile
+ remoteFile = ReadFileRemoteOperation(filePath).execute(nextcloudClient).resultData
sutResult = ReadFileVersionsRemoteOperation(remoteFile.localId).execute(client)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/SearchRemoteOperationIT.java b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/SearchRemoteOperationIT.java
index dda961ed4..94577c8e9 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/SearchRemoteOperationIT.java
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/SearchRemoteOperationIT.java
@@ -57,10 +57,10 @@ public void testSearchByFileIdEmpty() {
public void testSearchByFileIdSuccess() {
assertTrue(new CreateFolderRemoteOperation("/test/", true).execute(client).isSuccess());
- RemoteOperationResult readFile = new ReadFileRemoteOperation("/test/").execute(client);
+ RemoteOperationResult readFile = new ReadFileRemoteOperation("/test/").execute(nextcloudClient);
assertTrue(readFile.isSuccess());
- RemoteFile remoteFile = ((RemoteFile) readFile.getSingleData());
+ RemoteFile remoteFile = readFile.getResultData();
SearchRemoteOperation sut = new SearchRemoteOperation(String.valueOf(remoteFile.getLocalId()),
SearchRemoteOperation.SearchType.FILE_ID_SEARCH,
false,
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperationIT.kt
index 065674de3..24ffc4659 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/UploadFileRemoteOperationIT.kt
@@ -52,10 +52,10 @@ class UploadFileRemoteOperationIT : AbstractIT() {
assertTrue(uploadResult.isSuccess)
// ReadFileRemoteOperation
- var result = ReadFileRemoteOperation(remotePath).execute(client)
+ val result = ReadFileRemoteOperation(remotePath).execute(nextcloudClient)
assertTrue(result.isSuccess)
- var remoteFile = result.data[0] as RemoteFile
+ var remoteFile = result.resultData
assertEquals(remotePath, remoteFile.remotePath)
assertEquals(creationTimestamp, remoteFile.creationTimestamp)
@@ -66,10 +66,10 @@ class UploadFileRemoteOperationIT : AbstractIT() {
)
// ReadFolderRemoteOperation
- result = ReadFolderRemoteOperation(remotePath).execute(client)
- assertTrue(result.isSuccess)
+ var result2 = ReadFolderRemoteOperation(remotePath).execute(client)
+ assertTrue(result2.isSuccess)
- remoteFile = result.data[0] as RemoteFile
+ remoteFile = result2.data[0] as RemoteFile
assertEquals(remotePath, remoteFile.remotePath)
assertEquals(creationTimestamp, remoteFile.creationTimestamp)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/DeleteTagRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/DeleteTagRemoteOperationIT.kt
index 673a1b665..452922c22 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/DeleteTagRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/DeleteTagRemoteOperationIT.kt
@@ -29,8 +29,8 @@ class DeleteTagRemoteOperationIT : AbstractIT() {
// create a folder
val folder = "/deleteTagFolder/"
assertTrue(CreateFolderRemoteOperation(folder, true).execute(client).isSuccess)
- val folderMetadata = ReadFileRemoteOperation(folder).execute(client)
- val fileId = (folderMetadata.data[0] as RemoteFile).localId
+ val folderMetadata = ReadFileRemoteOperation(folder).execute(nextcloudClient)
+ val fileId = folderMetadata.resultData.localId
// create a tag
val tagName = RandomStringGenerator.make(TAG_LENGTH)
diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperationIT.kt
index e518a1959..6df323fde 100644
--- a/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperationIT.kt
+++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperationIT.kt
@@ -108,17 +108,17 @@ class GetTagsRemoteOperationIT : AbstractIT() {
// add colored tag to file
val tagFolder = "/coloredFolder/"
assertTrue(CreateFolderRemoteOperation(tagFolder, true).execute(client).isSuccess)
- val folderMetadata = ReadFileRemoteOperation(tagFolder).execute(client)
+ val folderMetadata = ReadFileRemoteOperation(tagFolder).execute(nextcloudClient)
assertTrue(
PutTagRemoteOperation(
tag1.id,
- (folderMetadata.data[0] as RemoteFile).localId
+ folderMetadata.resultData.localId
).execute(nextcloudClient).isSuccess
)
assertTrue(
PutTagRemoteOperation(
tag2.id,
- (folderMetadata.data[0] as RemoteFile).localId
+ folderMetadata.resultData.localId
).execute(nextcloudClient).isSuccess
)
diff --git a/library/src/main/java/com/nextcloud/common/DavResponse.kt b/library/src/main/java/com/nextcloud/common/DavResponse.kt
new file mode 100644
index 000000000..5ef3a0846
--- /dev/null
+++ b/library/src/main/java/com/nextcloud/common/DavResponse.kt
@@ -0,0 +1,39 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Tobias Kaminsky
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.nextcloud.common
+
+import okhttp3.Headers
+import okhttp3.internal.http.StatusLine
+
+/**
+ * Encapsulates essential data returned as responses from various DAV calls.
+ */
+data class DavResponse(
+ var success: Boolean = false,
+ var status: StatusLine? = null,
+ var headers: Headers? = null
+) {
+ /**
+ * Return value of specified header.
+ *
+ * Simple helper to aid with nullability when called from Java.
+ *
+ * @param key name of header to get
+ * @return value of header or `null` when header is not set
+ */
+ fun getHeader(key: String): String? = headers?.get(key)
+
+ /**
+ * Return value of status code.
+ *
+ * Simple helper to aid with nullability when called from Java.
+ *
+ * @return HTTP status code or `0` if not set.
+ */
+ fun getStatusCode(): Int = status?.code ?: 0
+}
diff --git a/library/src/main/java/com/nextcloud/common/NextcloudAuthenticator.kt b/library/src/main/java/com/nextcloud/common/NextcloudAuthenticator.kt
new file mode 100644
index 000000000..1b1528e33
--- /dev/null
+++ b/library/src/main/java/com/nextcloud/common/NextcloudAuthenticator.kt
@@ -0,0 +1,52 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Your Name
+ * SPDX-License-Identifier: MIT
+ */
+package com.nextcloud.common
+
+import okhttp3.Authenticator
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.Route
+
+class NextcloudAuthenticator(
+ private val credentials: String
+) : Authenticator {
+ @Suppress("ReturnCount")
+ override fun authenticate(
+ route: Route?,
+ response: Response
+ ): Request? {
+ val authenticatorType = "Authorization"
+
+ if (response.request.header(authenticatorType) != null) {
+ return null
+ }
+
+ var countedResponse: Response? = response
+
+ var attemptsCount = 0
+
+ countedResponse = countedResponse?.priorResponse
+
+ while (countedResponse != null) {
+ attemptsCount++
+ if (attemptsCount == MAX_ATTEMPTS) {
+ return null
+ }
+
+ countedResponse = countedResponse.priorResponse
+ }
+
+ return response.request
+ .newBuilder()
+ .header(authenticatorType, credentials)
+ .build()
+ }
+
+ companion object {
+ const val MAX_ATTEMPTS = 3
+ }
+}
diff --git a/library/src/main/java/com/nextcloud/common/NextcloudClient.kt b/library/src/main/java/com/nextcloud/common/NextcloudClient.kt
index 7f51e6437..8bf798329 100644
--- a/library/src/main/java/com/nextcloud/common/NextcloudClient.kt
+++ b/library/src/main/java/com/nextcloud/common/NextcloudClient.kt
@@ -82,6 +82,8 @@ class NextcloudClient private constructor(
Log_OC.d(this, "Proxy settings: $proxyHost:$proxyPort")
}
+ val userAgent = OwnCloudClientManagerFactory.getUserAgent()
+
return OkHttpClient
.Builder()
.cookieJar(CookieJar.NO_COOKIES)
@@ -94,7 +96,15 @@ class NextcloudClient private constructor(
.hostnameVerifier { _: String?, _: SSLSession? -> true }
.fastFallback(true)
.proxy(proxy)
- .build()
+ .addNetworkInterceptor { chain ->
+ chain.proceed(
+ chain
+ .request()
+ .newBuilder()
+ .header("User-Agent", userAgent)
+ .build()
+ )
+ }.build()
}
}
@@ -191,6 +201,13 @@ class NextcloudClient private constructor(
return result
}
+ fun disabledRedirectClient(): OkHttpClient =
+ client
+ .newBuilder()
+ .followRedirects(false)
+ .authenticator(NextcloudAuthenticator(credentials))
+ .build()
+
private fun setRedirectedDestinationHeader(
method: OkHttpMethodBase,
location: String,
diff --git a/library/src/main/java/com/nextcloud/common/WebDavUtils.kt b/library/src/main/java/com/nextcloud/common/WebDavUtils.kt
new file mode 100644
index 000000000..5dfceddeb
--- /dev/null
+++ b/library/src/main/java/com/nextcloud/common/WebDavUtils.kt
@@ -0,0 +1,415 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Tobias Kaminsky
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.nextcloud.common
+
+import android.net.Uri
+import at.bitfire.dav4jvm.PropertyRegistry
+import at.bitfire.dav4jvm.Response
+import at.bitfire.dav4jvm.property.DisplayName
+import at.bitfire.dav4jvm.property.GetContentLength
+import at.bitfire.dav4jvm.property.GetContentType
+import at.bitfire.dav4jvm.property.GetETag
+import at.bitfire.dav4jvm.property.ResourceType
+import com.google.gson.Gson
+import com.owncloud.android.lib.common.network.WebdavEntry
+import com.owncloud.android.lib.resources.files.model.RemoteFile
+import com.owncloud.android.lib.resources.files.webdav.NCCreationDate
+import com.owncloud.android.lib.resources.files.webdav.NCEncrypted
+import com.owncloud.android.lib.resources.files.webdav.NCFavorite
+import com.owncloud.android.lib.resources.files.webdav.NCGetLastModified
+import com.owncloud.android.lib.resources.files.webdav.NCHidden
+import com.owncloud.android.lib.resources.files.webdav.NCLock
+import com.owncloud.android.lib.resources.files.webdav.NCLockOwner
+import com.owncloud.android.lib.resources.files.webdav.NCLockOwnerDisplayName
+import com.owncloud.android.lib.resources.files.webdav.NCLockOwnerEditor
+import com.owncloud.android.lib.resources.files.webdav.NCLockOwnerType
+import com.owncloud.android.lib.resources.files.webdav.NCLockTime
+import com.owncloud.android.lib.resources.files.webdav.NCLockTimeout
+import com.owncloud.android.lib.resources.files.webdav.NCLockToken
+import com.owncloud.android.lib.resources.files.webdav.NCMetadataGPS
+import com.owncloud.android.lib.resources.files.webdav.NCMetadataLivePhoto
+import com.owncloud.android.lib.resources.files.webdav.NCMetadataPhotosGPS
+import com.owncloud.android.lib.resources.files.webdav.NCMetadataPhotosSize
+import com.owncloud.android.lib.resources.files.webdav.NCMetadataSize
+import com.owncloud.android.lib.resources.files.webdav.NCMountType
+import com.owncloud.android.lib.resources.files.webdav.NCNote
+import com.owncloud.android.lib.resources.files.webdav.NCPermissions
+import com.owncloud.android.lib.resources.files.webdav.NCPreview
+import com.owncloud.android.lib.resources.files.webdav.NCRichWorkspace
+import com.owncloud.android.lib.resources.files.webdav.NCSharees
+import com.owncloud.android.lib.resources.files.webdav.NCTags
+import com.owncloud.android.lib.resources.files.webdav.NCTrashbinDeletionTime
+import com.owncloud.android.lib.resources.files.webdav.NCTrashbinFilename
+import com.owncloud.android.lib.resources.files.webdav.NCTrashbinLocation
+import com.owncloud.android.lib.resources.files.webdav.NCUploadTime
+import com.owncloud.android.lib.resources.files.webdav.OCCommentsUnread
+import com.owncloud.android.lib.resources.files.webdav.OCDisplayName
+import com.owncloud.android.lib.resources.files.webdav.OCId
+import com.owncloud.android.lib.resources.files.webdav.OCLocalId
+import com.owncloud.android.lib.resources.files.webdav.OCOwnerDisplayName
+import com.owncloud.android.lib.resources.files.webdav.OCOwnerId
+import com.owncloud.android.lib.resources.files.webdav.OCSize
+import java.net.URLDecoder
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+object WebDavUtils {
+ const val NAMESPACE_OC = "http://owncloud.org/ns"
+ const val NAMESPACE_NC = "http://nextcloud.org/ns"
+
+ internal val gson = Gson()
+
+ private val DATETIME_FORMATS =
+ arrayOf(
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US),
+ SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'", Locale.US),
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US),
+ SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US),
+ SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US),
+ SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.US)
+ )
+
+ object PROPERTYSETS {
+ val ALL =
+ arrayOf(
+ DisplayName.NAME,
+ GetContentType.NAME,
+ ResourceType.NAME,
+ GetContentLength.NAME,
+ NCGetLastModified.NAME,
+ NCCreationDate.NAME,
+ GetETag.NAME,
+ NCPermissions.NAME,
+ OCLocalId.NAME,
+ OCId.NAME,
+ OCSize.NAME,
+ NCFavorite.NAME,
+ NCEncrypted.NAME,
+ NCMountType.NAME,
+ OCOwnerId.NAME,
+ OCOwnerDisplayName.NAME,
+ OCCommentsUnread.NAME,
+ NCPreview.NAME,
+ NCNote.NAME,
+ NCSharees.NAME,
+ NCRichWorkspace.NAME,
+ NCCreationDate.NAME,
+ NCUploadTime.NAME,
+ NCLock.NAME,
+ NCLockOwnerType.NAME,
+ NCLockOwner.NAME,
+ NCLockOwnerDisplayName.NAME,
+ NCLockOwnerEditor.NAME,
+ NCLockTime.NAME,
+ NCLockTimeout.NAME,
+ NCLockToken.NAME,
+ NCTags.NAME,
+ NCMetadataSize.NAME,
+ NCMetadataGPS.NAME,
+ NCMetadataSize.NAME,
+ NCMetadataPhotosSize.NAME,
+ NCMetadataPhotosGPS.NAME,
+ NCMetadataLivePhoto.NAME,
+ NCHidden.NAME
+ )
+
+ val FILE =
+ arrayOf(
+ DisplayName.NAME,
+ GetContentType.NAME,
+ ResourceType.NAME,
+ GetContentLength.NAME,
+ NCGetLastModified.NAME,
+ NCCreationDate.NAME,
+ GetETag.NAME,
+ NCPermissions.NAME,
+ OCLocalId.NAME,
+ OCId.NAME,
+ OCSize.NAME,
+ NCFavorite.NAME,
+ NCPreview.NAME,
+ NCSharees.NAME,
+ NCUploadTime.NAME,
+ NCLock.NAME,
+ NCLockOwnerType.NAME,
+ NCLockOwner.NAME,
+ NCLockOwnerDisplayName.NAME,
+ NCLockOwnerEditor.NAME,
+ NCLockTime.NAME,
+ NCLockTimeout.NAME,
+ NCLockToken.NAME,
+ NCEncrypted.NAME,
+ NCTags.NAME,
+ NCMetadataSize.NAME,
+ NCMetadataGPS.NAME,
+ NCMetadataPhotosSize.NAME,
+ NCMetadataPhotosGPS.NAME,
+ NCMetadataLivePhoto.NAME,
+ NCHidden.NAME
+ )
+
+ val TRASHBIN =
+ arrayOf(
+ ResourceType.NAME,
+ GetContentType.NAME,
+ GetContentLength.NAME,
+ OCSize.NAME,
+ OCId.NAME,
+ NCTrashbinFilename.NAME,
+ NCTrashbinLocation.NAME,
+ NCTrashbinDeletionTime.NAME
+ )
+
+ val FILE_VERSION =
+ arrayOf(
+ GetContentType.NAME,
+ ResourceType.NAME,
+ GetContentLength.NAME,
+ NCGetLastModified.NAME,
+ NCCreationDate.NAME,
+ OCId.NAME,
+ OCSize.NAME
+ )
+
+ val CHUNK =
+ arrayOf(
+ GetContentType.NAME,
+ ResourceType.NAME,
+ GetContentLength.NAME
+ )
+ }
+
+ fun registerCustomFactories() {
+ val list =
+ listOf(
+ NCCreationDate.Factory(),
+ NCEncrypted.Factory(),
+ GetETag.Factory(),
+ NCFavorite.Factory(),
+ NCGetLastModified.Factory(),
+ NCHidden.Factory(),
+ NCLock.Factory(),
+ NCLockOwnerDisplayName.Factory(),
+ NCLockOwnerEditor.Factory(),
+ NCLockOwner.Factory(),
+ NCLockOwnerType.Factory(),
+ NCLockTime.Factory(),
+ NCLockTimeout.Factory(),
+ NCLockToken.Factory(),
+ NCMetadataGPS.Factory(),
+ NCMetadataLivePhoto.Factory(),
+ NCMetadataPhotosGPS.Factory(),
+ NCMetadataPhotosSize.Factory(),
+ NCMetadataSize.Factory(),
+ NCMountType.Factory(),
+ NCNote.Factory(),
+ NCPermissions.Factory(),
+ NCPreview.Factory(),
+ NCRichWorkspace.Factory(),
+ NCSharees.Factory(),
+ NCTags.Factory(),
+ NCTrashbinDeletionTime.Factory(),
+ NCTrashbinFilename.Factory(),
+ NCTrashbinLocation.Factory(),
+ NCUploadTime.Factory(),
+ OCCommentsUnread.Factory(),
+ OCDisplayName.Factory(),
+ OCId.Factory(),
+ OCLocalId.Factory(),
+ OCOwnerDisplayName.Factory(),
+ OCOwnerId.Factory(),
+ OCSize.Factory()
+ )
+ PropertyRegistry.register(list)
+ }
+
+ @Suppress("LongMethod")
+ fun parseResponse(
+ response: Response,
+ filesDavUri: Uri
+ ): RemoteFile {
+ val remoteFile = RemoteFile()
+
+ val path = "/" + URLDecoder.decode(response.href.toString().substringAfter(filesDavUri.toString()), "UTF-8")
+
+ for (property in response.properties) {
+ when (property) {
+ is DisplayName -> {
+ remoteFile.name = property.displayName ?: ""
+ }
+
+ is GetContentLength -> {
+ remoteFile.length = property.contentLength
+ }
+
+ is GetContentType -> {
+ remoteFile.mimeType = (property.type ?: "").toString()
+ }
+
+ is ResourceType -> {
+ if (property.types.contains(ResourceType.COLLECTION)) {
+ remoteFile.mimeType = WebdavEntry.DIR_TYPE
+ }
+ }
+
+ is NCCreationDate -> {
+ remoteFile.creationTimestamp = property.creationDate
+ }
+
+ is NCEncrypted -> {
+ remoteFile.isEncrypted = property.encrypted
+ }
+
+ is GetETag -> {
+ remoteFile.etag = property.eTag
+ }
+
+ is NCFavorite -> {
+ remoteFile.isFavorite = property.favorite
+ }
+
+ is NCGetLastModified -> {
+ remoteFile.modifiedTimestamp = property.lastModified
+ }
+
+ is NCHidden -> {
+ remoteFile.hidden = property.hidden
+ }
+
+ is NCLock -> {
+ remoteFile.isLocked = property.locked
+ }
+
+ is NCLockOwner -> {
+ remoteFile.lockOwner = property.lockOwner
+ }
+
+ is NCLockOwnerDisplayName -> {
+ remoteFile.lockOwnerDisplayName = property.lockOwnerDisplayName
+ }
+
+ is NCLockOwnerEditor -> {
+ remoteFile.lockOwnerEditor = property.lockOwnerEditor
+ }
+
+ is NCLockOwnerType -> {
+ remoteFile.lockType = property.lockOwnerType
+ }
+
+ is NCLockTime -> {
+ remoteFile.lockTimestamp = property.lockTime
+ }
+
+ is NCLockTimeout -> {
+ remoteFile.lockTimeout = property.lockTimeout
+ }
+
+ is NCLockToken -> {
+ remoteFile.lockToken = property.lockToken
+ }
+
+ is NCMetadataGPS -> {
+ remoteFile.geoLocation = property.geoLocation
+ }
+
+ is NCMetadataLivePhoto -> {
+ remoteFile.livePhoto = property.livePhoto
+ }
+
+ is NCMetadataPhotosGPS -> {
+ remoteFile.geoLocation = property.geoLocation
+ }
+
+ is NCMetadataPhotosSize -> {
+ remoteFile.imageDimension = property.imageDimension
+ }
+
+ is NCMetadataSize -> {
+ remoteFile.imageDimension = property.imageDimension
+ }
+
+ is NCMountType -> {
+ remoteFile.mountType = property.mountType
+ }
+
+ is NCNote -> {
+ remoteFile.note = property.note
+ }
+
+ is NCPermissions -> {
+ remoteFile.permissions = property.permissions
+ }
+
+ is NCPreview -> {
+ remoteFile.isHasPreview = property.preview
+ }
+
+ is NCRichWorkspace -> {
+ remoteFile.richWorkspace = property.richWorkspace
+ }
+
+ is NCSharees -> {
+ remoteFile.sharees = property.sharees
+ }
+
+ is NCTags -> {
+ remoteFile.tags = property.tags
+ }
+
+ is NCTrashbinDeletionTime -> { /* TODO */ }
+
+ is NCTrashbinFilename -> { /* TODO */ }
+
+ is NCTrashbinLocation -> { /* TODO */ }
+
+ is NCUploadTime -> {
+ remoteFile.uploadTimestamp = property.uploadTime
+ }
+
+ is OCCommentsUnread -> {
+ remoteFile.unreadCommentsCount = property.commentsCount
+ }
+
+ is OCDisplayName -> {
+ remoteFile.name = property.displayName
+ }
+
+ is OCId -> {
+ remoteFile.remoteId = property.id
+ }
+
+ is OCLocalId -> {
+ remoteFile.localId = property.localId
+ }
+
+ is OCOwnerDisplayName -> {
+ remoteFile.ownerDisplayName = property.ownerDisplayName ?: ""
+ }
+
+ is OCOwnerId -> {
+ remoteFile.ownerId = property.ownerId ?: ""
+ }
+
+ is OCSize -> {
+ remoteFile.size = property.size
+ }
+ }
+ }
+
+ remoteFile.remotePath = path
+
+ // displayName not set - get from path
+ if (remoteFile.name?.isEmpty() == true) {
+ remoteFile.name = path.substringAfterLast("/")
+ }
+
+ return remoteFile
+ }
+}
diff --git a/library/src/main/java/com/nextcloud/operations/PropFindMethod2.kt b/library/src/main/java/com/nextcloud/operations/PropFindMethod2.kt
new file mode 100644
index 000000000..0cacee709
--- /dev/null
+++ b/library/src/main/java/com/nextcloud/operations/PropFindMethod2.kt
@@ -0,0 +1,10 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Tobias Kaminsky
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.nextcloud.operations
+
+class PropFindMethod2
diff --git a/library/src/main/java/com/nextcloud/operations/PropFindResult.kt b/library/src/main/java/com/nextcloud/operations/PropFindResult.kt
new file mode 100644
index 000000000..0dcfe646f
--- /dev/null
+++ b/library/src/main/java/com/nextcloud/operations/PropFindResult.kt
@@ -0,0 +1,19 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Tobias Kaminsky
+ * SPDX-License-Identifier: MIT
+ */
+
+package com.nextcloud.operations
+
+import com.nextcloud.common.DavResponse
+import com.owncloud.android.lib.resources.files.model.RemoteFile
+
+data class PropFindResult(
+ val davResponse: DavResponse = DavResponse(),
+ var root: RemoteFile = RemoteFile(),
+ val children: MutableList = mutableListOf()
+) {
+ fun getContent(): List = listOf(root) + children
+}
diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt b/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt
new file mode 100644
index 000000000..ce48ddf77
--- /dev/null
+++ b/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt
@@ -0,0 +1,59 @@
+/*
+ * Nextcloud Android Library
+ *
+ * SPDX-FileCopyrightText: 2026 Your Name
+ * SPDX-License-Identifier: MIT
+ */
+package com.owncloud.android.lib.common.network
+
+import at.bitfire.dav4jvm.Property
+
+enum class ExtendedProperties(
+ val value: String,
+ val namespace: String
+) {
+ COMMENTS_READ_MARKER("readMarker", WebdavUtils.NAMESPACE_NC),
+ DISPLAY_NAME("display-name", WebdavUtils.NAMESPACE_OC),
+ FAVORITE("favorite", WebdavUtils.NAMESPACE_OC),
+ HAS_PREVIEW("has-preview", WebdavUtils.NAMESPACE_NC),
+ HIDDEN("hidden", WebdavUtils.NAMESPACE_NC),
+ IS_ENCRYPTED("is-encrypted", WebdavUtils.NAMESPACE_NC),
+ LOCK("lock", WebdavUtils.NAMESPACE_NC),
+ LOCK_OWNER("lock-owner", WebdavUtils.NAMESPACE_NC),
+ LOCK_OWNER_DISPLAY_NAME("lock-owner-displayname", WebdavUtils.NAMESPACE_NC),
+ LOCK_OWNER_EDITOR("lock-owner-editor", WebdavUtils.NAMESPACE_NC),
+ LOCK_OWNER_TYPE("lock-owner-type", WebdavUtils.NAMESPACE_NC),
+ LOCK_TIME("lock-time", WebdavUtils.NAMESPACE_NC),
+ LOCK_TIMEOUT("lock-timeout", WebdavUtils.NAMESPACE_NC),
+ LOCK_TOKEN("lock-token", WebdavUtils.NAMESPACE_NC),
+
+ @Deprecated("Removed with v28. Use METADATA_PHOTOS_GPS instead.")
+ METADATA_GPS("file-metadata-gps", WebdavUtils.NAMESPACE_NC),
+ METADATA_LIVE_PHOTO("metadata-files-live-photo", WebdavUtils.NAMESPACE_NC),
+ METADATA_PHOTOS_GPS("metadata-photos-gps", WebdavUtils.NAMESPACE_NC),
+ METADATA_PHOTOS_SIZE("metadata-photos-size", WebdavUtils.NAMESPACE_NC),
+
+ @Deprecated("Removed with v28. Use METADATA_PHOTOS_SIZE instead.")
+ METADATA_SIZE("file-metadata-size", WebdavUtils.NAMESPACE_NC),
+ MOUNT_TYPE("mount-type", WebdavUtils.NAMESPACE_NC),
+ NAME_LOCAL_ID("fileid", WebdavUtils.NAMESPACE_OC),
+ NAME_PERMISSIONS("permissions", WebdavUtils.NAMESPACE_OC),
+ NAME_REMOTE_ID("id", WebdavUtils.NAMESPACE_OC),
+ NAME_SIZE("size", WebdavUtils.NAMESPACE_OC),
+ NOTE("note", WebdavUtils.NAMESPACE_NC),
+ OWNER_DISPLAY_NAME("owner-display-name", WebdavUtils.NAMESPACE_OC),
+ OWNER_ID("owner-id", WebdavUtils.NAMESPACE_OC),
+ RICH_WORKSPACE("rich-workspace", WebdavUtils.NAMESPACE_NC),
+ SHAREES("sharees", WebdavUtils.NAMESPACE_NC),
+ SHAREES_DISPLAY_NAME("display-name", WebdavUtils.NAMESPACE_NC),
+ SHAREES_ID("id", WebdavUtils.NAMESPACE_NC),
+ SHAREES_SHARE_TYPE("type", WebdavUtils.NAMESPACE_NC),
+ SYSTEM_TAGS("system-tags", WebdavUtils.NAMESPACE_NC),
+ TRASHBIN_DELETION_TIME("trashbin-deletion-time", WebdavUtils.NAMESPACE_NC),
+ TRASHBIN_FILENAME("trashbin-filename", WebdavUtils.NAMESPACE_NC),
+ TRASHBIN_ORIGINAL_LOCATION("trashbin-original-location", WebdavUtils.NAMESPACE_NC),
+ UNREAD_COMMENTS("comments-unread", WebdavUtils.NAMESPACE_OC),
+ UPLOAD_TIME("upload_time", WebdavUtils.NAMESPACE_NC);
+
+ fun toPropertyName(): Property.Name = Property.Name(namespace, value)
+}
diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt
index 45178b9ed..eece446f1 100644
--- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt
+++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavEntry.kt
@@ -153,7 +153,7 @@ class WebdavEntry constructor(
if (prop != null) {
val value = prop.value
if (value != null) {
- contentType = "DIR" // a specific attribute would be better,
+ contentType = DIR_TYPE // a specific attribute would be better,
// but this is enough;
// unless while we have no reason to distinguish
// MIME types for folders
@@ -613,7 +613,7 @@ class WebdavEntry constructor(
fun decodedPath(): String = Uri.decode(path)
val isDirectory: Boolean
- get() = "DIR" == contentType
+ get() = DIR_TYPE == contentType
private fun resetData() {
permissions = null
@@ -690,6 +690,7 @@ class WebdavEntry constructor(
const val PROPERTY_DATE_RANGE = "dateRange"
const val PROPERTY_COLLABORATORS = "collaborators"
const val COLLABORATORS_SHARE_LABEL = "label"
+ const val DIR_TYPE = "DIR"
private const val IS_ENCRYPTED = "1"
private const val CODE_PROP_NOT_FOUND = 404
}
diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java
index 338d7fafd..47a250ddd 100644
--- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java
+++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.java
@@ -16,6 +16,8 @@
import android.net.Uri;
+import androidx.annotation.Nullable;
+
import com.nextcloud.common.OkHttpMethodBase;
import org.apache.commons.httpclient.Header;
@@ -29,11 +31,13 @@
import java.util.Date;
import java.util.Locale;
-import androidx.annotation.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@SuppressFBWarnings("FS")
public class WebdavUtils {
+ public static String NAMESPACE_OC = "http://owncloud.org/ns";
+ public static String NAMESPACE_NC = "http://nextcloud.org/ns";
+
private static final SimpleDateFormat DATETIME_FORMATS[] = {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US),
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java
deleted file mode 100644
index 52a97a839..000000000
--- a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Nextcloud Android Library
- *
- * SPDX-FileCopyrightText: 2015 ownCloud Inc.
- * SPDX-License-Identifier: MIT
- */
-package com.owncloud.android.lib.resources.files;
-
-import com.nextcloud.common.SessionTimeOut;
-import com.nextcloud.common.SessionTimeOutKt;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.network.WebdavEntry;
-import com.owncloud.android.lib.common.network.WebdavUtils;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.model.RemoteFile;
-
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.jackrabbit.webdav.DavConstants;
-import org.apache.jackrabbit.webdav.MultiStatus;
-import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
-
-import java.util.ArrayList;
-
-
-/**
- * Remote operation performing the read a file from the ownCloud server.
- *
- * @author David A. Velasco
- * @author masensio
- */
-
-public class ReadFileRemoteOperation extends RemoteOperation {
-
- private static final String TAG = ReadFileRemoteOperation.class.getSimpleName();
- private final String mRemotePath;
- private final SessionTimeOut sessionTimeOut;
-
-
- /**
- * Constructor
- *
- * @param remotePath Remote path of the file.
- */
- public ReadFileRemoteOperation(String remotePath) {
- this(remotePath, SessionTimeOutKt.getDefaultSessionTimeOut());
- }
-
- public ReadFileRemoteOperation(String remotePath, SessionTimeOut sessionTimeOut) {
- mRemotePath = remotePath;
- this.sessionTimeOut = sessionTimeOut;
- }
-
- /**
- * Performs the read operation.
- *
- * @param client Client object to communicate with the remote ownCloud server.
- */
- @Override
- protected RemoteOperationResult run(OwnCloudClient client) {
- PropFindMethod propfind = null;
- RemoteOperationResult result = null;
-
- /// take the duty of check the server for the current state of the file there
- try {
- // remote request
- propfind = new PropFindMethod(client.getFilesDavUri(mRemotePath),
- WebdavUtils.getFilePropSet(), // PropFind Properties
- DavConstants.DEPTH_0);
- int status;
- status = client.executeMethod(propfind, sessionTimeOut.getReadTimeOut(), sessionTimeOut.getConnectionTimeOut());
-
- boolean isSuccess = (
- status == HttpStatus.SC_MULTI_STATUS ||
- status == HttpStatus.SC_OK
- );
- if (isSuccess) {
- // Parse response
- MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
- WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
- client.getFilesDavUri().getEncodedPath());
- RemoteFile remoteFile = new RemoteFile(we);
- ArrayList