1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-09 08:56:22 +00:00

Merge pull request #366 from owncloud/master

1.0.9 stable
This commit is contained in:
Abel García de Prada 2021-01-26 13:11:29 +01:00 committed by GitHub
commit c36096d08c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 637 additions and 162 deletions

View File

@ -1,6 +1,6 @@
buildscript {
ext {
kotlinVersion = '1.3.72'
kotlinVersion = '1.4.20'
moshiVersion = "1.9.2"
}
@ -9,9 +9,8 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
}
}

View File

@ -1,3 +1,3 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx1536M

View File

@ -1,25 +1,20 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-allopen'
dependencies {
api 'com.squareup.okhttp3:okhttp:4.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5'
api 'com.github.AppDevNext.Logcat:LogcatCore:2.1.1'
api 'net.openid:appauth:0.7.1'
api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2'
// Moshi
implementation ("com.squareup.moshi:moshi-kotlin:$moshiVersion") {
exclude module: "kotlin-reflect"
}
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
}
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'com.owncloud.android.lib.testing.OpenClass'
testImplementation 'junit:junit:4.13.1'
}
android {
@ -29,12 +24,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode = 10000800
versionName = "1.0.8"
// This is pretty ugly but manifest placeholders don't seem to work very well when using different modules
// See https://github.com/openid/AppAuth-Android/issues/325
manifestPlaceholders = [appAuthRedirectScheme: '']
versionCode = 10000900
versionName = "1.0.9"
}
lintOptions {

View File

@ -57,6 +57,7 @@ public class HttpClient {
private static OkHttpClient sOkHttpClient;
private static Context sContext;
private static HashMap<String, List<Cookie>> sCookieStore = new HashMap<>();
private static LogInterceptor sLogInterceptor;
public static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) {
@ -110,6 +111,7 @@ public class HttpClient {
};
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.addNetworkInterceptor(getLogInterceptor())
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
@ -120,6 +122,7 @@ public class HttpClient {
.cookieJar(cookieJar);
// TODO: Not verifying the hostname against certificate. ask owncloud security human if this is ok.
//.hostnameVerifier(new BrowserCompatHostnameVerifier());
sOkHttpClient = clientBuilder.build();
} catch (Exception e) {
@ -137,6 +140,13 @@ public class HttpClient {
sContext = context;
}
public static LogInterceptor getLogInterceptor() {
if (sLogInterceptor == null) {
sLogInterceptor = new LogInterceptor();
}
return sLogInterceptor;
}
public List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
return sCookieStore.get(httpUrl.host());
}

View File

@ -51,6 +51,14 @@ public class HttpConstants {
public static final String ACCEPT_ENCODING_IDENTITY = "identity";
public static final String OC_FILE_REMOTE_ID = "OC-FileId";
/***********************************************************************************************************
************************************************ CONTENT TYPES ********************************************
***********************************************************************************************************/
public static final String CONTENT_TYPE_XML = "application/xml";
public static final String CONTENT_TYPE_JSON = "application/json";
public static final String CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded";
/***********************************************************************************************************
************************************************ STATUS CODES *********************************************
***********************************************************************************************************/

View File

@ -0,0 +1,65 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.common.http
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_JSON
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_WWW_FORM
import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_XML
import okhttp3.MediaType
import timber.log.Timber
import java.util.Locale
object LogBuilder {
fun logHttp(
networkPetition: NetworkPetition,
networkNode: NetworkNode,
requestId: String? = "",
description: String
) = Timber.d("[Network, $networkPetition] [$networkNode] [$requestId] $description")
}
enum class NetworkPetition {
REQUEST, RESPONSE;
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT)
}
enum class NetworkNode {
INFO, HEADER, BODY;
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT)
}
/**
* Check whether a media type is loggable.
*
* @return true if its type is text, xml, json, or x-www-form-urlencoded.
*/
fun MediaType?.isLoggable(): Boolean =
this?.let { mediaType ->
val mediaTypeString = mediaType.toString()
(mediaType.type == "text" ||
mediaTypeString.contains(CONTENT_TYPE_XML) ||
mediaTypeString.contains(CONTENT_TYPE_JSON) ||
mediaTypeString.contains(CONTENT_TYPE_WWW_FORM))
} ?: false

View File

@ -0,0 +1,179 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http
import com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER
import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
import com.owncloud.android.lib.common.http.LogBuilder.logHttp
import com.owncloud.android.lib.common.http.NetworkNode.BODY
import com.owncloud.android.lib.common.http.NetworkNode.HEADER
import com.owncloud.android.lib.common.http.NetworkNode.INFO
import com.owncloud.android.lib.common.http.NetworkPetition.REQUEST
import com.owncloud.android.lib.common.http.NetworkPetition.RESPONSE
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.ResponseBody
import okio.Buffer
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import kotlin.math.max
class LogInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
if (!httpLogsEnabled) {
return chain.proceed(chain.request())
}
val request = chain.request().also {
val requestId = it.headers[OC_X_REQUEST_ID]
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}")
logHeaders(requestId, it.headers, REQUEST)
logRequestBody(requestId, it.body)
}
val response = chain.proceed(request)
return response.also {
val requestId = it.request.headers[OC_X_REQUEST_ID]
logHttp(
RESPONSE,
INFO,
requestId,
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}"
)
logHeaders(requestId, it.headers, RESPONSE)
logResponseBody(requestId, it.body)
}
}
private fun logHeaders(requestId: String?, headers: Headers, networkPetition: NetworkPetition) {
headers.forEach { header ->
val headerValue: String = if (header.first.equals(AUTHORIZATION_HEADER, true)) {
"[redacted]"
} else {
header.second
}
logHttp(networkPetition, HEADER, requestId, "${header.first}: $headerValue")
}
}
private fun logRequestBody(requestId: String?, requestBodyParam: RequestBody?) {
requestBodyParam?.let { requestBody ->
if (requestBody.isOneShot()) {
logHttp(REQUEST, BODY, requestId, "One shot body -- Omitted")
return@let
}
if (requestBody.isDuplex()) {
logHttp(REQUEST, BODY, requestId, "Duplex body -- Omitted")
return@let
}
val buffer = Buffer()
requestBody.writeTo(buffer)
val contentType = requestBody.contentType()
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
logHttp(REQUEST, BODY, requestId, "Length: ${requestBody.contentLength()} byte body")
logHttp(REQUEST, BODY, requestId, "Type: ${requestBody.contentType()}")
logHttp(REQUEST, BODY, requestId, "--> Body start for request")
if (contentType.isLoggable()) {
if (requestBody.contentLength() < LIMIT_BODY_LOG) {
logHttp(REQUEST, BODY, requestId, buffer.readString(charset))
} else {
logHttp(REQUEST, BODY, requestId, buffer.readString(LIMIT_BODY_LOG, charset))
}
logHttp(
REQUEST,
BODY,
requestId,
"<-- Body end for request -- Omitted: ${max(0, requestBody.contentLength() - LIMIT_BODY_LOG)} bytes"
)
} else {
logHttp(
REQUEST,
BODY,
requestId,
"<-- Body end for request -- Binary -- Omitted: ${requestBody.contentLength()} bytes"
)
}
} ?: logHttp(REQUEST, BODY, requestId, "Empty body")
}
private fun logResponseBody(requestId: String?, responseBodyParam: ResponseBody?) {
responseBodyParam?.let { responseBody ->
val contentType = responseBody.contentType()
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
logHttp(RESPONSE, BODY, requestId, "Length: ${responseBody.contentLength()} byte body")
logHttp(RESPONSE, BODY, requestId, "Type: ${responseBody.contentType()}")
logHttp(RESPONSE, BODY, requestId, "--> Body start for response")
val source = responseBody.source()
source.request(LIMIT_BODY_LOG)
val buffer = source.buffer
if (contentType.isLoggable()) {
if (responseBody.contentLength() < LIMIT_BODY_LOG) {
logHttp(RESPONSE, BODY, requestId, buffer.clone().readString(charset))
} else {
logHttp(RESPONSE, BODY, requestId, buffer.clone().readString(LIMIT_BODY_LOG, charset))
}
logHttp(
RESPONSE,
BODY,
requestId,
"<-- Body end for response -- Omitted: ${
max(
0,
responseBody.contentLength() - LIMIT_BODY_LOG
)
} bytes"
)
} else {
logHttp(
RESPONSE,
BODY,
requestId,
"<-- Body end for response -- Binary -- Omitted: ${responseBody.contentLength()} bytes"
)
}
} ?: logHttp(RESPONSE, BODY, requestId, "Empty body")
}
companion object {
var httpLogsEnabled: Boolean = false
private const val LIMIT_BODY_LOG: Long = 1024
}
}

View File

@ -84,18 +84,12 @@ public class ChunkFromFileRequestBody extends FileRequestBody {
long maxCount = Math.min(mOffset + mChunkSize, mChannel.size());
while (mChannel.position() < maxCount) {
Timber.v("Sink buffer size: %s", sink.buffer().size());
readCount = mChannel.read(mBuffer);
Timber.v("Read " + readCount + " bytes from file channel to " + mBuffer.toString());
sink.buffer().write(mBuffer.array(), 0, readCount);
sink.getBuffer().write(mBuffer.array(), 0, readCount);
sink.flush();
Timber.v("Write " + readCount + " bytes to sink buffer with size " + sink.buffer().size());
mBuffer.clear();
if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
mTransferred += readCount;

View File

@ -53,6 +53,11 @@ public class FileRequestBody extends RequestBody implements ProgressiveDataTrans
mContentType = contentType;
}
@Override
public boolean isOneShot() {
return true;
}
@Override
public MediaType contentType() {
return mContentType;

View File

@ -21,8 +21,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.response
package com.owncloud.android.lib.resources
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// Response retrieved by OCS Rest API, used to obtain capabilities, shares and user info among others.
@ -41,6 +42,11 @@ data class OCSResponse<T>(
@JsonClass(generateAdapter = true)
data class MetaData(
val status: String,
val statuscode: Int,
val message: String?
@Json(name = "statuscode")
val statusCode: Int,
val message: String?,
@Json(name = "itemsperpage")
val itemsPerPage: String?,
@Json(name = "totalitems")
val totalItems: String?
)

View File

@ -30,6 +30,8 @@ import java.io.File;
public class FileUtils {
public static final String FINAL_CHUNKS_FILE = ".file";
public static final String MIME_DIR = "DIR";
public static final String MIME_DIR_UNIX = "httpd/unix-directory";
static String getParentPath(String remotePath) {
String parentPath = new File(remotePath).getParent();

View File

@ -29,7 +29,17 @@ import android.os.Parcelable;
import at.bitfire.dav4jvm.Property;
import at.bitfire.dav4jvm.Response;
import at.bitfire.dav4jvm.property.*;
import at.bitfire.dav4jvm.property.CreationDate;
import at.bitfire.dav4jvm.property.GetContentLength;
import at.bitfire.dav4jvm.property.GetContentType;
import at.bitfire.dav4jvm.property.GetETag;
import at.bitfire.dav4jvm.property.GetLastModified;
import at.bitfire.dav4jvm.property.OCId;
import at.bitfire.dav4jvm.property.OCPermissions;
import at.bitfire.dav4jvm.property.OCPrivatelink;
import at.bitfire.dav4jvm.property.OCSize;
import at.bitfire.dav4jvm.property.QuotaAvailableBytes;
import at.bitfire.dav4jvm.property.QuotaUsedBytes;
import java.io.File;
import java.io.Serializable;
@ -83,7 +93,8 @@ public class RemoteFile implements Parcelable, Serializable {
/**
* Create new {@link RemoteFile} with given path.
* <p>
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first character in 'path'.
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first
* character in 'path'.
*
* @param path The remote path of the file.
*/
@ -95,7 +106,7 @@ public class RemoteFile implements Parcelable, Serializable {
mRemotePath = path;
mCreationTimestamp = 0;
mLength = 0;
mMimeType = "DIR";
mMimeType = FileUtils.MIME_DIR;
mQuotaUsedBytes = BigDecimal.ZERO;
mQuotaAvailableBytes = BigDecimal.ZERO;
mPrivateLink = null;
@ -154,6 +165,14 @@ public class RemoteFile implements Parcelable, Serializable {
readFromParcel(source);
}
/**
* Use this to find out if this file is a folder.
*
* @return true if it is a folder
*/
public boolean isFolder() {
return mMimeType != null && (mMimeType.equals(FileUtils.MIME_DIR) || mMimeType.equals(FileUtils.MIME_DIR_UNIX));
}
/**
* Getters and Setters

View File

@ -46,9 +46,9 @@ import java.net.URL;
public class GetRemoteShareOperation extends RemoteOperation<ShareParserResult> {
private long mRemoteId;
private String mRemoteId;
public GetRemoteShareOperation(long remoteId) {
public GetRemoteShareOperation(String remoteId) {
mRemoteId = remoteId;
}
@ -60,7 +60,7 @@ public class GetRemoteShareOperation extends RemoteOperation<ShareParserResult>
Uri requestUri = client.getBaseUri();
Uri.Builder uriBuilder = requestUri.buildUpon();
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH);
uriBuilder.appendEncodedPath(Long.toString(mRemoteId));
uriBuilder.appendEncodedPath(mRemoteId);
GetMethod getMethod = new GetMethod(new URL(uriBuilder.build().toString()));
getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);

View File

@ -1,5 +1,6 @@
/* ownCloud Android Library is available under MIT license
*
* @author Christian Schabesberger
* @author masensio
* @author David A. Velasco
* @author David González Verdugo
@ -28,16 +29,21 @@
package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK
import org.json.JSONObject
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.responses.ShareeOcsResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL
import java.util.ArrayList
/**
* Created by masensio on 08/10/2015.
@ -65,6 +71,7 @@ import java.util.ArrayList
* Status codes:
* 100 - successful
*
* @author Christian Schabesberger
* @author masensio
* @author David A. Velasco
* @author David González Verdugo
@ -78,80 +85,66 @@ class GetRemoteShareesOperation
* @param perPage maximum number of results in a single page
*/
(private val searchString: String, private val page: Int, private val perPage: Int) :
RemoteOperation<ArrayList<JSONObject>>() {
RemoteOperation<ShareeOcsResponse>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<ArrayList<JSONObject>> {
var result: RemoteOperationResult<ArrayList<JSONObject>>
private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.appendQueryParameter(PARAM_ITEM_TYPE, VALUE_ITEM_TYPE)
.appendQueryParameter(PARAM_SEARCH, searchString)
.appendQueryParameter(PARAM_PAGE, page.toString())
.appendQueryParameter(PARAM_PER_PAGE, perPage.toString())
.build()
try {
val requestUri = client.baseUri
val uriBuilder = requestUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.appendQueryParameter(PARAM_ITEM_TYPE, VALUE_ITEM_TYPE)
.appendQueryParameter(PARAM_SEARCH, searchString)
.appendQueryParameter(PARAM_PAGE, page.toString())
.appendQueryParameter(PARAM_PER_PAGE, perPage.toString())
private fun parseResponse(response: String): ShareeOcsResponse? {
val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type)
return adapter.fromJson(response)!!.ocs.data
}
val getMethod = GetMethod(URL(uriBuilder.build().toString()))
private fun onResultUnsuccessful(
method: GetMethod,
response: String?,
status: Int
): RemoteOperationResult<ShareeOcsResponse> {
Timber.e("Failed response while getting users/groups from the server ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> {
val result = RemoteOperationResult<ShareeOcsResponse>(OK)
Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Get Users or groups completed ")
return result
}
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareeOcsResponse> {
val requestUri = buildRequestUri(client.baseUri)
val getMethod = GetMethod(URL(requestUri.toString()))
getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
return try {
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (isSuccess(status)) {
Timber.d("Successful response: $response")
// Parse the response
val respJSON = JSONObject(response)
val respOCS = respJSON.getJSONObject(NODE_OCS)
val respData = respOCS.getJSONObject(NODE_DATA)
val respExact = respData.getJSONObject(NODE_EXACT)
val respExactUsers = respExact.getJSONArray(NODE_USERS)
val respExactGroups = respExact.getJSONArray(NODE_GROUPS)
val respExactRemotes = respExact.getJSONArray(NODE_REMOTES)
val respPartialUsers = respData.getJSONArray(NODE_USERS)
val respPartialGroups = respData.getJSONArray(NODE_GROUPS)
val respPartialRemotes = respData.getJSONArray(NODE_REMOTES)
val jsonResults = arrayOf(
respExactUsers,
respExactGroups,
respExactRemotes,
respPartialUsers,
respPartialGroups,
respPartialRemotes
)
val data = ArrayList<JSONObject>() // For result data
for (i in 0..5) {
for (j in 0 until jsonResults[i].length()) {
val jsonResult = jsonResults[i].getJSONObject(j)
data.add(jsonResult)
Timber.d("*** Added item: ${jsonResult.getString(PROPERTY_LABEL)}")
}
}
result = RemoteOperationResult(OK)
result.data = data
Timber.d("*** Get Users or groups completed ")
if (!isSuccess(status)) {
onResultUnsuccessful(getMethod, response, status)
} else {
result = RemoteOperationResult(getMethod)
Timber.e("Failed response while getting users/groups from the server ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
onRequestSuccessful(response)
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while getting users/groups")
RemoteOperationResult(e)
}
return result
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
@ -171,18 +164,5 @@ class GetRemoteShareesOperation
// Arguments - constant values
private const val VALUE_FORMAT = "json"
private const val VALUE_ITEM_TYPE = "file" // to get the server search for users / groups
// JSON Node names
private const val NODE_OCS = "ocs"
private const val NODE_DATA = "data"
private const val NODE_EXACT = "exact"
private const val NODE_USERS = "users"
private const val NODE_GROUPS = "groups"
private const val NODE_REMOTES = "remotes"
const val NODE_VALUE = "value"
const val PROPERTY_LABEL = "label"
const val PROPERTY_SHARE_TYPE = "shareType"
const val PROPERTY_SHARE_WITH = "shareWith"
const val PROPERTY_SHARE_WITH_ADDITIONAL_INFO = "shareWithAdditionalInfo"
}
}

View File

@ -24,7 +24,6 @@
package com.owncloud.android.lib.resources.shares
import com.owncloud.android.lib.resources.files.FileUtils
import java.io.File
/**
@ -35,7 +34,7 @@ import java.io.File
* @author David González Verdugo
*/
data class RemoteShare(
var id: Long = 0,
var id: String = "0",
var shareWith: String = "",
var path: String = "",
var token: String = "",
@ -43,15 +42,11 @@ data class RemoteShare(
var sharedWithAdditionalInfo: String = "",
var name: String = "",
var shareLink: String = "",
var fileSource: String = "0",
var itemSource: String = "0",
var shareType: ShareType? = ShareType.UNKNOWN,
var permissions: Int = DEFAULT_PERMISSION,
var sharedDate: Long = INIT_SHARED_DATE,
var expirationDate: Long = INIT_EXPIRATION_DATE_IN_MILLIS,
var isFolder: Boolean = path.endsWith(File.separator),
var userId: Long = 0,
val isValid: Boolean = id > -1
var isFolder: Boolean = path.endsWith(File.separator)
) {
companion object {
@ -106,17 +101,6 @@ enum class ShareType constructor(val value: Int) {
FEDERATED(6);
companion object {
fun fromValue(value: Int): ShareType? {
return when (value) {
-1 -> UNKNOWN
0 -> USER
1 -> GROUP
3 -> PUBLIC_LINK
4 -> EMAIL
5 -> CONTACT
6 -> FEDERATED
else -> null
}
}
fun fromValue(value: Int) = values().firstOrNull { it.value == value }
}
}

View File

@ -48,7 +48,7 @@ import java.net.URL
*
* @param remoteShareId Share ID
*/
class RemoveRemoteShareOperation(private val remoteShareId: Long) : RemoteOperation<ShareParserResult>() {
class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<ShareParserResult>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareParserResult> {
var result: RemoteOperationResult<ShareParserResult>
@ -57,7 +57,7 @@ class RemoveRemoteShareOperation(private val remoteShareId: Long) : RemoteOperat
val requestUri = client.baseUri
val uriBuilder = requestUri.buildUpon()
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH)
uriBuilder.appendEncodedPath(remoteShareId.toString())
uriBuilder.appendEncodedPath(remoteShareId)
val deleteMethod = DeleteMethod(
URL(uriBuilder.build().toString())

View File

@ -179,7 +179,7 @@ class ShareXMLParser {
name.equals(NODE_ID, ignoreCase = true) -> {// Parse Create XML Response
share = RemoteShare()
val value = readNode(parser, NODE_ID)
share.id = Integer.parseInt(value).toLong()
share.id = value
}
name.equals(NODE_URL, ignoreCase = true) -> {
// NOTE: this field is received in all the public shares from OC 9.0.0
@ -236,7 +236,7 @@ class ShareXMLParser {
}
name.equals(NODE_ID, ignoreCase = true) -> {
remoteShare.id = Integer.parseInt(readNode(parser, NODE_ID)).toLong()
remoteShare.id = readNode(parser, NODE_ID)
}
name.equals(NODE_ITEM_TYPE, ignoreCase = true) -> {
@ -244,10 +244,6 @@ class ShareXMLParser {
fixPathForFolder(remoteShare)
}
name.equals(NODE_ITEM_SOURCE, ignoreCase = true) -> {
remoteShare.itemSource = readNode(parser, NODE_ITEM_SOURCE)
}
name.equals(NODE_PARENT, ignoreCase = true) -> {
readNode(parser, NODE_PARENT)
}
@ -261,10 +257,6 @@ class ShareXMLParser {
remoteShare.shareWith = readNode(parser, NODE_SHARE_WITH)
}
name.equals(NODE_FILE_SOURCE, ignoreCase = true) -> {
remoteShare.fileSource = readNode(parser, NODE_FILE_SOURCE)
}
name.equals(NODE_PATH, ignoreCase = true) -> {
remoteShare.path = readNode(parser, NODE_PATH)
fixPathForFolder(remoteShare)
@ -320,9 +312,7 @@ class ShareXMLParser {
}
}
if (remoteShare.isValid) {
shares.add(remoteShare)
}
shares.add(remoteShare)
}
private fun fixPathForFolder(share: RemoteShare) {
@ -403,11 +393,9 @@ class ShareXMLParser {
private const val NODE_ELEMENT = "element"
private const val NODE_ID = "id"
private const val NODE_ITEM_TYPE = "item_type"
private const val NODE_ITEM_SOURCE = "item_source"
private const val NODE_PARENT = "parent"
private const val NODE_SHARE_TYPE = "share_type"
private const val NODE_SHARE_WITH = "share_with"
private const val NODE_FILE_SOURCE = "file_source"
private const val NODE_PATH = "path"
private const val NODE_PERMISSIONS = "permissions"
private const val NODE_STIME = "stime"

View File

@ -55,7 +55,7 @@ class UpdateRemoteShareOperation
/**
* @param remoteId Identifier of the share to update.
*/
private val remoteId: Long
private val remoteId: String
) : RemoteOperation<ShareParserResult>() {
/**

View File

@ -0,0 +1,72 @@
/* ownCloud Android Library is available under MIT license
*
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.shares.responses
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This was modeled according to the documentation:
* https://doc.owncloud.com/server/developer_manual/core/apis/ocs-recipient-api.html#get-shares-recipients
*/
@JsonClass(generateAdapter = true)
data class ShareeOcsResponse(
val exact: ExactSharees?,
val groups: List<ShareeItem>,
val remotes: List<ShareeItem>,
val users: List<ShareeItem>
) {
fun getFlatRepresentationWithoutExact() = ArrayList<ShareeItem>().apply {
addAll(users)
addAll(remotes)
addAll(groups)
}
}
@JsonClass(generateAdapter = true)
data class ExactSharees(
val groups: List<ShareeItem>,
val remotes: List<ShareeItem>,
val users: List<ShareeItem>
) {
fun getFlatRepresentation() = ArrayList<ShareeItem>().apply {
addAll(users)
addAll(remotes)
addAll(groups)
}
}
@JsonClass(generateAdapter = true)
data class ShareeItem(
val label: String,
val value: ShareeValue
)
@JsonClass(generateAdapter = true)
data class ShareeValue(
val shareType: Int,
val shareWith: String,
@Json(name = "shareWithAdditionalInfo")
val additionalInfo: String?
)

View File

@ -44,7 +44,7 @@ interface ShareService : Service {
): RemoteOperationResult<ShareParserResult>
fun updateShare(
remoteId: Long,
remoteId: String,
name: String,
password: String?,
expirationDate: Long,
@ -52,5 +52,5 @@ interface ShareService : Service {
publicUpload: Boolean
): RemoteOperationResult<ShareParserResult>
fun deleteShare(remoteId: Long): RemoteOperationResult<ShareParserResult>
fun deleteShare(remoteId: String): RemoteOperationResult<ShareParserResult>
}

View File

@ -1,6 +1,7 @@
/**
* ownCloud Android client application
*
* @author Christian Schabesberger
* @author David González Verdugo
*
* Copyright (C) 2020 ownCloud GmbH.
@ -22,13 +23,12 @@ package com.owncloud.android.lib.resources.shares.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import org.json.JSONObject
import java.util.ArrayList
import com.owncloud.android.lib.resources.shares.responses.ShareeOcsResponse
interface ShareeService : Service {
fun getSharees(
searchString: String,
page: Int,
perPage: Int
): RemoteOperationResult<ArrayList<JSONObject>>
): RemoteOperationResult<ShareeOcsResponse>
}

View File

@ -66,7 +66,7 @@ class OCShareService(override val client: OwnCloudClient) :
}.execute(client)
override fun updateShare(
remoteId: Long,
remoteId: String,
name: String,
password: String?,
expirationDate: Long,
@ -84,7 +84,7 @@ class OCShareService(override val client: OwnCloudClient) :
this.retrieveShareDetails = true
}.execute(client)
override fun deleteShare(remoteId: Long): RemoteOperationResult<ShareParserResult> =
override fun deleteShare(remoteId: String): RemoteOperationResult<ShareParserResult> =
RemoveRemoteShareOperation(
remoteId
).execute(client)

View File

@ -23,9 +23,8 @@ package com.owncloud.android.lib.resources.shares.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.shares.GetRemoteShareesOperation
import com.owncloud.android.lib.resources.shares.responses.ShareeOcsResponse
import com.owncloud.android.lib.resources.shares.services.ShareeService
import org.json.JSONObject
import java.util.ArrayList
class OCShareeService(override val client: OwnCloudClient) :
ShareeService {
@ -33,7 +32,7 @@ class OCShareeService(override val client: OwnCloudClient) :
searchString: String,
page: Int,
perPage: Int
): RemoteOperationResult<ArrayList<JSONObject>> =
): RemoteOperationResult<ShareeOcsResponse> =
GetRemoteShareesOperation(
searchString,
page,

View File

@ -34,8 +34,8 @@ import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK
import com.owncloud.android.lib.resources.response.CapabilityResponse
import com.owncloud.android.lib.resources.response.CommonOcsResponse
import com.owncloud.android.lib.resources.status.responses.CapabilityResponse
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types

View File

@ -22,7 +22,7 @@
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.response
package com.owncloud.android.lib.resources.status.responses
import com.owncloud.android.lib.resources.status.RemoteCapability
import com.owncloud.android.lib.resources.status.RemoteCapability.CapabilityBooleanType

View File

@ -29,8 +29,8 @@ import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.resources.response.CommonOcsResponse
import com.owncloud.android.lib.resources.response.UserInfoResponse
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.users.responses.UserInfoResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types

View File

@ -21,7 +21,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.response
package com.owncloud.android.lib.resources.users.responses
import com.owncloud.android.lib.resources.users.RemoteUserInfo
import com.squareup.moshi.Json

View File

@ -0,0 +1,93 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.shares.responses
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import org.junit.Assert.assertNull
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.File
import java.lang.reflect.Type
class ShareeResponseTest {
lateinit var adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>>
private fun loadResponses(fileName: String) =
adapter.fromJson(File(fileName).readText())
@Before
fun prepare() {
val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java)
adapter = moshi.adapter(type)
}
@Test
fun `check structure - ok - contains meta`() {
val response = loadResponses(EXAMPLE_RESPONSE_JSON)!!
assertEquals("OK", response.ocs.meta.message!!)
assertEquals(200, response.ocs.meta.statusCode)
assertEquals("ok", response.ocs.meta.status)
assertTrue(response.ocs.meta.itemsPerPage?.isEmpty()!!)
assertTrue(response.ocs.meta.totalItems?.isEmpty()!!)
}
@Test
fun `example response - ok - correct sturcture`() {
val response = loadResponses(EXAMPLE_RESPONSE_JSON)!!
assertEquals(2, response.ocs.data.groups.size)
assertEquals(0, response.ocs.data.remotes.size)
assertEquals(2, response.ocs.data.users.size)
assertEquals(0, response.ocs.data.exact?.groups?.size)
assertEquals(0, response.ocs.data.exact?.remotes?.size)
assertEquals(1, response.ocs.data.exact?.users?.size)
assertEquals("user1@user1.com", response.ocs.data.users.get(0).value.additionalInfo)
assertNull(response.ocs.data.users[1].value.additionalInfo)
}
@Test
fun `check empty response - ok - parsing ok`() {
val response = loadResponses(EMPTY_RESPONSE_JSON)!!
assertTrue(response.ocs.data.exact?.groups?.isEmpty()!!)
assertTrue(response.ocs.data.exact?.remotes?.isEmpty()!!)
assertTrue(response.ocs.data.exact?.users?.isEmpty()!!)
assertTrue(response.ocs.data.groups.isEmpty())
assertTrue(response.ocs.data.remotes.isEmpty())
assertTrue(response.ocs.data.users.isEmpty())
}
companion object {
val RESOURCES_PATH =
"src/test/responses/com.owncloud.android.lib.resources.sharees.responses"
val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json"
val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json"
}
}

View File

@ -0,0 +1,21 @@
{
"ocs": {
"meta": {
"status": "ok",
"statuscode": 100,
"message": "OK",
"totalitems": "",
"itemsperpage": ""
},
"data": {
"exact": {
"users": [],
"groups": [],
"remotes": []
},
"users": [],
"groups": [],
"remotes": []
}
}
}

View File

@ -0,0 +1,60 @@
{
"ocs": {
"data": {
"exact": {
"groups": [],
"remotes": [],
"users": [
{
"label": "admin",
"value": {
"shareType": 0,
"shareWith": "admin"
}
}
]
},
"groups": [
{
"label": "group1",
"value": {
"shareType": 1,
"shareWith": "group1"
}
},
{
"label": "group2",
"value": {
"shareType": 1,
"shareWith": "group2"
}
}
],
"remotes": [],
"users": [
{
"label": "user1",
"value": {
"shareType": 0,
"shareWith": "user1",
"shareWithAdditionalInfo": "user1@user1.com"
}
},
{
"label": "user2",
"value": {
"shareType": 0,
"shareWith": "user2"
}
}
]
},
"meta": {
"itemsperpage": "",
"message": "OK",
"status": "ok",
"statuscode": 200,
"totalitems": ""
}
}
}