Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Any>
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]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class UpdateShareRemoteOperationIT : AbstractIT() {
"",
OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER,
true,
""
"",
null
).execute(client)

assertTrue(createOperationResult.isSuccess)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class CreateShareRemoteOperation extends RemoteOperation<List<OCShare>> {
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;
Expand All @@ -46,6 +47,7 @@ public class CreateShareRemoteOperation extends RemoteOperation<List<OCShare>> {
private final int permissions;
private boolean getShareDetails;
private String note;
private String attributes;

/**
* Constructor
Expand All @@ -67,6 +69,7 @@ public class CreateShareRemoteOperation extends RemoteOperation<List<OCShare>> {
* 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,
Expand All @@ -76,7 +79,8 @@ public CreateShareRemoteOperation(
String password,
int permissions,
boolean getShareDetails,
String note
String note,
String attributes
) {
this.remoteFilePath = remoteFilePath;
this.shareType = shareType;
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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() {
Expand Down Expand Up @@ -158,6 +164,10 @@ protected RemoteOperationResult<List<OCShare>> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class OCShare :
var ownerDisplayName: String? = null
var isFavorite = false
var fileDownloadLimit: FileDownloadLimit? = null
var attributes: String? = null

constructor() : super() {
resetData()
Expand Down Expand Up @@ -114,6 +115,7 @@ class OCShare :
mimetype = ""
ownerDisplayName = ""
fileDownloadLimit = null
attributes = null
}

/**
Expand Down Expand Up @@ -154,6 +156,7 @@ class OCShare :
mimetype = source.readString()
ownerDisplayName = source.readString()
fileDownloadLimit = source.readSerializableCompat()
attributes = source.readString()
}

override fun describeContents(): Int = this.hashCode()
Expand Down Expand Up @@ -184,6 +187,7 @@ class OCShare :
dest.writeString(mimetype)
dest.writeString(ownerDisplayName)
dest.writeSerializable(fileDownloadLimit)
dest.writeString(attributes)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Date;
import java.util.List;


/**
* Parser for Share API Response
*
Expand Down Expand Up @@ -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 = "_$!<Favorite>";

Expand Down Expand Up @@ -418,6 +420,10 @@ private void readElement(XmlPullParser parser, ArrayList<OCShare> shares)
}
break;

case NODE_ATTRIBUTES:
share.setAttributes(readText(parser));
break;

default:
skip(parser);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";


/**
Expand Down Expand Up @@ -78,6 +79,7 @@ public class UpdateShareRemoteOperation extends RemoteOperation {

private String note;
private String label;
private String attributes;


/**
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -181,6 +187,10 @@ protected RemoteOperationResult<List<OCShare>> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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<ShareAttributes>?.getDownloadAttribute(): ShareAttributes? =
this?.find { it.key == ShareAttributes.DOWNLOAD_ATTRIBUTE_KEY }
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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<ShareAttributes> {
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)
}
}
Loading
Loading