From a2ec671aee087b61b99fa6970686bf2bb5e81ea8 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Mon, 9 Nov 2026 12:57:21 +0100 Subject: [PATCH 1/5] Implemented external storage mounts list remoteOperation Signed-off-by: daniele-verducci --- ...xternalStorageListMountsRemoteOperation.kt | 55 +++++++++++++++++++ .../model/ExternalStorageConfig.kt | 21 +++++++ .../model/ExternalStorageMount.kt | 31 +++++++++++ .../externalstorage/model/MountScope.kt | 20 +++++++ 4 files changed, 127 insertions(+) create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/ExternalStorageListMountsRemoteOperation.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageMount.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/MountScope.kt diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/ExternalStorageListMountsRemoteOperation.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/ExternalStorageListMountsRemoteOperation.kt new file mode 100644 index 0000000000..d94a1081f4 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/ExternalStorageListMountsRemoteOperation.kt @@ -0,0 +1,55 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage + +import com.nextcloud.android.lib.resources.externalstorage.model.ExternalStorageMount +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.ocs.OcsKotlinResponse +import com.owncloud.android.lib.ocs.ocsJson +import com.owncloud.android.lib.resources.OCSRemoteOperation +import kotlinx.serialization.SerializationException +import org.apache.commons.httpclient.HttpStatus +import java.io.IOException + +class ExternalStorageListMountsRemoteOperation : OCSRemoteOperation>() { + override fun run(client: NextcloudClient): RemoteOperationResult> { + val getMethod = GetMethod(client.baseUri.toString() + ENDPOINT + JSON_FORMAT, true) + return try { + val status = client.execute(getMethod) + if (status != HttpStatus.SC_OK) { + Log_OC.e(TAG, "Get external storage mounts failed with status code: $status") + return RemoteOperationResult(false, getMethod) + } + val response = + ocsJson.decodeFromString>>( + getMethod.getResponseBodyAsString() + ) + val data = response.ocs.data + RemoteOperationResult>(true, getMethod).apply { resultData = data } + } catch (e: IOException) { + failure(e) + } catch (e: SerializationException) { + failure(e) + } finally { + getMethod.releaseConnection() + } + } + + private fun failure(e: Exception): RemoteOperationResult> = + RemoteOperationResult>(e).also { + Log_OC.e(TAG, "Get external storage mounts failed: $e") + } + + companion object { + private val TAG = ExternalStorageListMountsRemoteOperation::class.java.simpleName + private const val ENDPOINT = "/ocs/v2.php/apps/files_external/api/v1/mounts" + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt new file mode 100644 index 0000000000..ce557b209b --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt @@ -0,0 +1,21 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage.model + +import kotlinx.serialization.Serializable + +/** + * Subset of the mount's `config` object. [authMechanism] is the auth identifier (e.g. + * `password::password`); [userProvided] is only sent by the server when the user must supply + * credentials themselves, hence the `false` default. + */ +@Serializable +data class ExternalStorageConfig( + val authMechanism: String, + val userProvided: Boolean = false +) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageMount.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageMount.kt new file mode 100644 index 0000000000..cb90051c49 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageMount.kt @@ -0,0 +1,31 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A mount returned by `files_external`: [name] is the mount folder, [path] its parent folder + * relative to the user's root (empty when mounted at the root), [backend] the human-readable backend + * name and [backendClass] its identifier (e.g. `smb`, `sftp`). [config] is null on servers that + * don't send it. + */ +@Serializable +data class ExternalStorageMount( + val id: Long, + val name: String, + val path: String, + val type: String, + val backend: String, + @SerialName("class") + val backendClass: String, + val scope: MountScope, + val permissions: Int, + val config: ExternalStorageConfig? = null +) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/MountScope.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/MountScope.kt new file mode 100644 index 0000000000..fcf3c10151 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/MountScope.kt @@ -0,0 +1,20 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class MountScope { + @SerialName("system") + SYSTEM, + + @SerialName("personal") + PERSONAL +} From 1963eae5baa5532d98a027bd9401fc4b353890ce Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Mon, 9 Nov 2026 18:38:45 +0100 Subject: [PATCH 2/5] Removed unused field Signed-off-by: daniele-verducci --- .../resources/externalstorage/model/ExternalStorageConfig.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt index ce557b209b..6b818d7cc0 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt @@ -16,6 +16,5 @@ import kotlinx.serialization.Serializable */ @Serializable data class ExternalStorageConfig( - val authMechanism: String, - val userProvided: Boolean = false + val authMechanism: String ) From 5bc10f3b786a02c534ace969db66139860f79d89 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Tue, 10 Nov 2026 09:33:49 +0100 Subject: [PATCH 3/5] Enum for auth, with values obtained from server code Signed-off-by: daniele-verducci --- .../externalstorage/model/AuthMechanism.kt | 53 +++++++++++++++++++ .../model/ExternalStorageConfig.kt | 6 +-- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt new file mode 100644 index 0000000000..5dd00b00bf --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt @@ -0,0 +1,53 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage.model + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Auth mechanisms registered by External Storage app. + * Represents the way an external storage mount is authenticated. + * + * I.e.: + * PasswordUserProvided --> User has to provide its credentials the first time the share is opened + * PasswordGlobal --> The admin provides the credentials and the user finds the share already mounted + * etc... + */ +@Serializable +enum class AuthMechanism( + val identifier: String +) { + Null("null::null"), + Builtin("builtin::builtin"), + Password("password::password"), + PasswordSessionCredentials("password::sessioncredentials"), + PasswordLoginCredentials("password::logincredentials"), + PasswordUserProvided("password::userprovided"), + PasswordGlobal("password::global"), + PasswordGlobalUser("password::global::user"), + PublicKeyRsa("publickey::rsa"), + PublicKeyRsaPrivate("publickey::rsa_private"), + AmazonS3AccessKey("amazons3::accesskey"), + OpenStack("openstack::openstack"), + OpenStackV3("openstack::openstackv3"), + OpenStackRackspace("openstack::rackspace"), + SmbKerberos("smb::kerberos"), + SmbKerberosApache("smb::kerberosapache"), + Unknown(""); + + companion object { + fun fromIdentifier(identifier: String): AuthMechanism = + entries.firstOrNull { it != Unknown && it.identifier == identifier } ?: Unknown + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt index 6b818d7cc0..41ced10546 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt @@ -10,11 +10,9 @@ package com.nextcloud.android.lib.resources.externalstorage.model import kotlinx.serialization.Serializable /** - * Subset of the mount's `config` object. [authMechanism] is the auth identifier (e.g. - * `password::password`); [userProvided] is only sent by the server when the user must supply - * credentials themselves, hence the `false` default. + * Subset of the mount's `config` object. */ @Serializable data class ExternalStorageConfig( - val authMechanism: String + val authMechanism: AuthMechanism ) From b718858990fa98eaa92bbf2017795dcadbab4a7b Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Fri, 13 Nov 2026 08:20:54 +0100 Subject: [PATCH 4/5] Serializer Signed-off-by: daniele-verducci --- .../externalstorage/model/AuthMechanism.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt index 5dd00b00bf..9ae5321fa4 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.encoding.Encoder * PasswordGlobal --> The admin provides the credentials and the user finds the share already mounted * etc... */ -@Serializable +@Serializable(with = AuthMechanismSerializer::class) enum class AuthMechanism( val identifier: String ) { @@ -51,3 +51,15 @@ enum class AuthMechanism( entries.firstOrNull { it != Unknown && it.identifier == identifier } ?: Unknown } } + +object AuthMechanismSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("AuthMechanism", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): AuthMechanism = AuthMechanism.fromIdentifier(decoder.decodeString()) + + override fun serialize( + encoder: Encoder, + value: AuthMechanism + ) = encoder.encodeString(value.identifier) +} From 942886477887bdca7085fe78780abdfe590ed6d0 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Fri, 13 Nov 2026 09:54:15 +0100 Subject: [PATCH 5/5] More idiomatic AuthMechanism Signed-off-by: daniele-verducci --- .../externalstorage/model/AuthMechanism.kt | 21 ------------------- .../model/ExternalStorageConfig.kt | 8 ++++++- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt index 9ae5321fa4..94ea2a618f 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt @@ -7,14 +7,6 @@ package com.nextcloud.android.lib.resources.externalstorage.model -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - /** * Auth mechanisms registered by External Storage app. * Represents the way an external storage mount is authenticated. @@ -24,7 +16,6 @@ import kotlinx.serialization.encoding.Encoder * PasswordGlobal --> The admin provides the credentials and the user finds the share already mounted * etc... */ -@Serializable(with = AuthMechanismSerializer::class) enum class AuthMechanism( val identifier: String ) { @@ -51,15 +42,3 @@ enum class AuthMechanism( entries.firstOrNull { it != Unknown && it.identifier == identifier } ?: Unknown } } - -object AuthMechanismSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("AuthMechanism", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): AuthMechanism = AuthMechanism.fromIdentifier(decoder.decodeString()) - - override fun serialize( - encoder: Encoder, - value: AuthMechanism - ) = encoder.encodeString(value.identifier) -} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt index 41ced10546..1d72e78d83 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt @@ -7,6 +7,7 @@ package com.nextcloud.android.lib.resources.externalstorage.model +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** @@ -14,5 +15,10 @@ import kotlinx.serialization.Serializable */ @Serializable data class ExternalStorageConfig( + // kept raw: identifiers from apps registering their own mechanisms map to Unknown instead of failing + @SerialName("authMechanism") + private val authMechanismIdentifier: String +) { val authMechanism: AuthMechanism -) + get() = AuthMechanism.fromIdentifier(authMechanismIdentifier) +}