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/AuthMechanism.kt b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt new file mode 100644 index 0000000000..94ea2a618f --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/AuthMechanism.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.externalstorage.model + +/** + * 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... + */ +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 new file mode 100644 index 0000000000..1d72e78d83 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/externalstorage/model/ExternalStorageConfig.kt @@ -0,0 +1,24 @@ +/* + * 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 + +/** + * Subset of the mount's `config` object. + */ +@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) +} 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 +}