diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperationIT.kt index 06bfbe7017..a239adf67d 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperationIT.kt @@ -9,11 +9,13 @@ package com.owncloud.android.lib.resources.shares import com.owncloud.android.AbstractIT import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation +import com.owncloud.android.lib.resources.shares.attributes.ShareAttributes +import com.owncloud.android.lib.resources.shares.attributes.ShareAttributesJsonHandler import com.owncloud.android.lib.resources.status.GetStatusRemoteOperation import com.owncloud.android.lib.resources.status.NextcloudVersion import com.owncloud.android.lib.resources.status.OwnCloudVersion -import org.junit.Assert import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assume import org.junit.Before import org.junit.Test @@ -22,40 +24,58 @@ class CreateShareRemoteOperationIT : AbstractIT() { @Before fun before() { val result = GetStatusRemoteOperation(context).execute(client) - Assert.assertTrue(result.isSuccess) + assertTrue(result.isSuccess) val data = result.data as ArrayList val ownCloudVersion = data[0] as OwnCloudVersion Assume.assumeTrue(ownCloudVersion.isNewerOrEqual(NextcloudVersion.nextcloud_24)) } + @Test + fun createShareWithNoteAndAttributes() { + val attributes = listOf(ShareAttributes.createDownloadAttributes(true)) + val note = "Note with attributes" + val path = "/shareWithAttributes/" + + createFolder(path) + val share = createShare(path, "admin", note, ShareAttributesJsonHandler.toJson(attributes)) + assertEquals(note, share.note) + assertEquals(attributes, ShareAttributesJsonHandler.toList(share.attributes)) + } + @Test fun createShareWithNote() { val note = "This is the note" + val path = "/share/" - Assert.assertTrue( - CreateFolderRemoteOperation( - "/share/", - true - ).execute(client).isSuccess - ) + createFolder(path) + val share = createShare(path, "admin", note) + assertEquals(note, share.note) + } - // share folder to user "admin" - val sut = + private fun createFolder(path: String) { + assertTrue(CreateFolderRemoteOperation(path, true).execute(client).isSuccess) + } + + private fun createShare( + path: String, + accountName: String, + note: String, + attributes: String? = null + ): OCShare { + val operation = CreateShareRemoteOperation( - "/share/", + path, ShareType.USER, - "admin", + accountName, false, "", OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, true, - note - ).execute(client) - - junit.framework.Assert.assertTrue(sut.isSuccess) - - val share = sut.resultData[0] - - assertEquals(note, share.note) + note, + attributes + ) + val result = operation.execute(client) + assertTrue(result.isSuccess) + return result.resultData[0] } } diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperationIT.kt index 368d9dc59f..dae6aa80e2 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperationIT.kt @@ -57,7 +57,8 @@ class UpdateShareRemoteOperationIT : AbstractIT() { "", OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER, true, - "" + "", + null ).execute(client) assertTrue(createOperationResult.isSuccess) diff --git a/library/src/main/java/com/nextcloud/extensions/JsonObjectExtensions.kt b/library/src/main/java/com/nextcloud/extensions/JsonObjectExtensions.kt new file mode 100644 index 0000000000..222777667f --- /dev/null +++ b/library/src/main/java/com/nextcloud/extensions/JsonObjectExtensions.kt @@ -0,0 +1,28 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.extensions + +import com.google.gson.JsonObject + +@Suppress("ReturnCount") +fun JsonObject?.getBoolean(key: String): Boolean? { + if (this == null) { + return null + } + + if (has(key) && get(key).isJsonPrimitive) { + return try { + get(key).asBoolean + } catch (_: UnsupportedOperationException) { + null + } + } + + return null +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperation.java index 0ef9a46e98..622d21e43c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/CreateShareRemoteOperation.java @@ -37,6 +37,7 @@ public class CreateShareRemoteOperation extends RemoteOperation> { private static final String PARAM_PASSWORD = "password"; private static final String PARAM_PERMISSIONS = "permissions"; private static final String PARAM_NOTE = "note"; + private static final String PARAM_ATTRIBUTES = "attributes"; private final String remoteFilePath; private final ShareType shareType; @@ -46,6 +47,7 @@ public class CreateShareRemoteOperation extends RemoteOperation> { private final int permissions; private boolean getShareDetails; private String note; + private String attributes; /** * Constructor @@ -67,6 +69,7 @@ public class CreateShareRemoteOperation extends RemoteOperation> { * For user or group shares. * To obtain combinations, add the desired values together. * For instance, for Re-Share, delete, read, update, add 16+8+2+1 = 27. + * @param attributes Share attributes are used for more advanced flags like permissions. */ public CreateShareRemoteOperation( String remoteFilePath, @@ -76,7 +79,8 @@ public CreateShareRemoteOperation( String password, int permissions, boolean getShareDetails, - String note + String note, + String attributes ) { this.remoteFilePath = remoteFilePath; this.shareType = shareType; @@ -86,6 +90,7 @@ public CreateShareRemoteOperation( this.permissions = permissions; this.getShareDetails = getShareDetails; // defaults to false for backwards compatibility this.note = note; + this.attributes = attributes; } public CreateShareRemoteOperation( @@ -95,7 +100,7 @@ public CreateShareRemoteOperation( boolean publicUpload, String password, int permissions) { - this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, false, ""); + this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, false, "", null); } public CreateShareRemoteOperation( @@ -105,8 +110,9 @@ public CreateShareRemoteOperation( boolean publicUpload, String password, int permissions, - String note) { - this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, false, note); + String note, + String attributes) { + this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, false, note, attributes); } public CreateShareRemoteOperation( @@ -117,7 +123,7 @@ public CreateShareRemoteOperation( String password, int permissions, boolean getShareDetails) { - this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, getShareDetails, ""); + this(remoteFilePath, shareType, shareWith, publicUpload, password, permissions, getShareDetails, "", null); } public boolean isGettingShareDetails() { @@ -158,6 +164,10 @@ protected RemoteOperationResult> run(OwnCloudClient client) { post.addParameter(PARAM_NOTE, note); } + if (!TextUtils.isEmpty(attributes)) { + post.addParameter(PARAM_ATTRIBUTES, attributes); + } + post.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE); status = client.executeMethod(post); diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/OCShare.kt b/library/src/main/java/com/owncloud/android/lib/resources/shares/OCShare.kt index 31aacfa9b4..50010ff080 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/OCShare.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/OCShare.kt @@ -73,6 +73,7 @@ class OCShare : var ownerDisplayName: String? = null var isFavorite = false var fileDownloadLimit: FileDownloadLimit? = null + var attributes: String? = null constructor() : super() { resetData() @@ -114,6 +115,7 @@ class OCShare : mimetype = "" ownerDisplayName = "" fileDownloadLimit = null + attributes = null } /** @@ -154,6 +156,7 @@ class OCShare : mimetype = source.readString() ownerDisplayName = source.readString() fileDownloadLimit = source.readSerializableCompat() + attributes = source.readString() } override fun describeContents(): Int = this.hashCode() @@ -184,6 +187,7 @@ class OCShare : dest.writeString(mimetype) dest.writeString(ownerDisplayName) dest.writeSerializable(fileDownloadLimit) + dest.writeString(attributes) } companion object { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java index ad7d08c49f..70c8de62f0 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.List; + /** * Parser for Share API Response * @@ -70,6 +71,7 @@ public class ShareXMLParser { private static final String NODE_DISPLAYNAME_FILE_OWNER = "displayname_file_owner"; private static final String NODE_TAGS = "tags"; private static final String NODE_URL = "url"; + private static final String NODE_ATTRIBUTES = "attributes"; private static final String TAG_FAVORITE = "_$!"; @@ -418,6 +420,10 @@ private void readElement(XmlPullParser parser, ArrayList shares) } break; + case NODE_ATTRIBUTES: + share.setAttributes(readText(parser)); + break; + default: skip(parser); break; diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperation.java index 830a677249..826c772848 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/UpdateShareRemoteOperation.java @@ -49,6 +49,7 @@ public class UpdateShareRemoteOperation extends RemoteOperation { private static final String FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"; private static final String ENTITY_CONTENT_TYPE = "application/x-www-form-urlencoded"; private static final String ENTITY_CHARSET = "UTF-8"; + private static final String PARAM_ATTRIBUTES = "attributes"; /** @@ -78,6 +79,7 @@ public class UpdateShareRemoteOperation extends RemoteOperation { private String note; private String label; + private String attributes; /** @@ -137,6 +139,10 @@ public void setLabel(String label) { this.label = label; } + public void setAttributes(String attributes) { + this.attributes = attributes; + } + public void setNote(String note) { this.note = note; } @@ -181,6 +187,10 @@ protected RemoteOperationResult> run(OwnCloudClient client) { parametersToUpdate.add(new Pair<>(PARAM_LABEL, URLEncoder.encode(label))); } + if (attributes != null) { + parametersToUpdate.add(new Pair<>(PARAM_ATTRIBUTES, URLEncoder.encode(attributes))); + } + /// perform required PUT requests PutMethod put = null; String uriString; diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributes.kt b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributes.kt new file mode 100644 index 0000000000..56c685e9fc --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributes.kt @@ -0,0 +1,25 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.shares.attributes + +data class ShareAttributes( + val scope: String, + val key: String, + var value: Boolean +) { + companion object { + const val DOWNLOAD_ATTRIBUTE_KEY = "download" + + fun createDownloadAttributes(value: Boolean): ShareAttributes = + ShareAttributes(scope = "permissions", key = DOWNLOAD_ATTRIBUTE_KEY, value = value) + } +} + +fun List?.getDownloadAttribute(): ShareAttributes? = + this?.find { it.key == ShareAttributes.DOWNLOAD_ATTRIBUTE_KEY } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesDeserializer.kt b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesDeserializer.kt new file mode 100644 index 0000000000..3457b1240a --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesDeserializer.kt @@ -0,0 +1,36 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.shares.attributes + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.nextcloud.extensions.getBoolean +import java.lang.reflect.Type + +/** + * Custom serializer for the ShareAttributes class. + * This handles the deserialization and serialization of the ShareAttributes data class. + * Since Nextcloud 30, the enabled key have been renamed to value and supports more than boolean. + * + * https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#share-attributes + */ +class ShareAttributesDeserializer : JsonDeserializer { + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): ShareAttributes? { + val jsonObject = json?.asJsonObject + val scope = jsonObject?.get("scope")?.asString ?: "" + val key = jsonObject?.get("key")?.asString ?: "" + val value = (jsonObject.getBoolean("value") ?: jsonObject.getBoolean("enabled")) == true + return ShareAttributes(scope, key, value) + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesJsonHandler.kt b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesJsonHandler.kt new file mode 100644 index 0000000000..50c4d5a68f --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/attributes/ShareAttributesJsonHandler.kt @@ -0,0 +1,36 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.resources.shares.attributes + +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken + +object ShareAttributesJsonHandler { + private val gson = + GsonBuilder() + .registerTypeAdapter(ShareAttributes::class.java, ShareAttributesDeserializer()) + .create() + + fun toList(jsonString: String?): List? { + if (jsonString == null) { + return null + } + + val listType = object : TypeToken>() {}.type + return gson.fromJson(jsonString, listType) + } + + fun toJson(shareAttributes: List?): String? { + if (shareAttributes == null) { + return null + } + + return gson.toJson(shareAttributes) + } +}