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

Merge pull request #567 from owncloud/release/4.0

[Release] 2.1
This commit is contained in:
Juan Carlos Garrote 2023-05-25 12:46:19 +02:00 committed by GitHub
commit d7260426d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1072 additions and 310 deletions

View File

@ -1,7 +1,7 @@
buildscript { buildscript {
ext { ext {
kotlinVersion = '1.6.21' orgJetbrainsKotlin = '1.8.10'
moshiVersion = "1.13.0" comSquareupMoshi = '1.14.0'
} }
repositories { repositories {
@ -10,12 +10,16 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" } maven { url "https://plugins.gradle.org/m2/" }
} }
dependencies { dependencies {
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.3.0" classpath "org.jlleitschuh.gradle:ktlint-gradle:11.1.0"
classpath 'com.android.tools.build:gradle:7.1.3' classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$orgJetbrainsKotlin"
} }
} }
plugins {
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
}
allprojects { allprojects {
repositories { repositories {
google() google()
@ -26,4 +30,5 @@ allprojects {
subprojects { subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint" apply plugin: "org.jlleitschuh.gradle.ktlint"
} apply plugin: "com.google.devtools.ksp"
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,32 +1,32 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'com.google.devtools.ksp'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
dependencies { dependencies {
api 'com.squareup.okhttp3:okhttp:4.6.0' api 'com.squareup.okhttp3:okhttp:4.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$orgJetbrainsKotlin"
api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5' api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5'
api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2' api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2'
// Moshi // Moshi
implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") { implementation("com.squareup.moshi:moshi-kotlin:$comSquareupMoshi") {
exclude module: "kotlin-reflect" exclude module: "kotlin-reflect"
} }
implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.commons:commons-lang3:3.12.0'
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" ksp "com.squareup.moshi:moshi-kotlin-codegen:$comSquareupMoshi"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.9' testImplementation 'org.robolectric:robolectric:4.10'
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
} }
android { android {
compileSdkVersion 31 compileSdkVersion 33
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 33
} }
lint { lint {
@ -39,4 +39,5 @@ android {
includeAndroidResources = true includeAndroidResources = true
} }
} }
namespace 'com.owncloud.android.lib'
} }

View File

@ -23,8 +23,7 @@
--> -->
<manifest package="com.owncloud.android.lib" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- USE_CREDENTIALS, MANAGE_ACCOUNTS and AUTHENTICATE_ACCOUNTS are needed for API < 23. <!-- USE_CREDENTIALS, MANAGE_ACCOUNTS and AUTHENTICATE_ACCOUNTS are needed for API < 23.
In API >= 23 the do not exist anymore --> In API >= 23 the do not exist anymore -->

View File

@ -37,7 +37,6 @@ import com.owncloud.android.lib.resources.status.RemoteServerInfo
import org.apache.commons.lang3.exception.ExceptionUtils import org.apache.commons.lang3.exception.ExceptionUtils
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import java.lang.Exception
/** /**
* ConnectionValidator * ConnectionValidator
@ -46,7 +45,7 @@ import java.lang.Exception
*/ */
class ConnectionValidator( class ConnectionValidator(
val context: Context, val context: Context,
val clearCookiesOnValidation: Boolean private val clearCookiesOnValidation: Boolean
) { ) {
fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager, context: Context): Boolean { fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager, context: Context): Boolean {
try { try {
@ -61,12 +60,12 @@ class ConnectionValidator(
client.account = baseClient.account client.account = baseClient.account
client.credentials = baseClient.credentials client.credentials = baseClient.credentials
while (validationRetryCount < VALIDATION_RETRY_COUNT) { while (validationRetryCount < VALIDATION_RETRY_COUNT) {
Timber.d("validationRetryCout %d", validationRetryCount) Timber.d("validationRetryCount %d", validationRetryCount)
var successCounter = 0 var successCounter = 0
var failCounter = 0 var failCounter = 0
client.setFollowRedirects(true) client.setFollowRedirects(true)
if (isOnwCloudStatusOk(client)) { if (isOwnCloudStatusOk(client)) {
successCounter++ successCounter++
} else { } else {
failCounter++ failCounter++
@ -103,7 +102,7 @@ class ConnectionValidator(
return false return false
} }
private fun isOnwCloudStatusOk(client: OwnCloudClient): Boolean { private fun isOwnCloudStatusOk(client: OwnCloudClient): Boolean {
val reply = getOwnCloudStatus(client) val reply = getOwnCloudStatus(client)
// dont check status code. It currently relais on the broken redirect code of the owncloud client // dont check status code. It currently relais on the broken redirect code of the owncloud client
// TODO: Use okhttp redirect and add this check again // TODO: Use okhttp redirect and add this check again
@ -138,6 +137,12 @@ class ConnectionValidator(
// test if have all the needed to effectively invalidate ... // test if have all the needed to effectively invalidate ...
shouldInvalidateAccountCredentials = shouldInvalidateAccountCredentials =
shouldInvalidateAccountCredentials and (account.savedAccount != null) shouldInvalidateAccountCredentials and (account.savedAccount != null)
Timber.d(
"""Received error: $httpStatusCode,
account: ${account.name}
credentials are real: ${credentials !is OwnCloudAnonymousCredentials},
so we need to invalidate credentials for account ${account.name} : $shouldInvalidateAccountCredentials"""
)
return shouldInvalidateAccountCredentials return shouldInvalidateAccountCredentials
} }
@ -150,6 +155,7 @@ class ConnectionValidator(
* *
*/ */
private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) { private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) {
Timber.i("Invalidating account credentials for account $account")
val am = AccountManager.get(context) val am = AccountManager.get(context)
am.invalidateAuthToken( am.invalidateAuthToken(
account.savedAccount.type, account.savedAccount.type,
@ -167,8 +173,6 @@ class ConnectionValidator(
* *
* Refresh current credentials if possible, and marks a retry. * Refresh current credentials if possible, and marks a retry.
* *
* @param status
* @param repeatCounter
* @return * @return
*/ */
private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean { private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean {
@ -181,6 +185,7 @@ class ConnectionValidator(
if (credentials.authTokenCanBeRefreshed()) { if (credentials.authTokenCanBeRefreshed()) {
try { try {
// This command does the actual refresh // This command does the actual refresh
Timber.i("Trying to refresh auth token for account $account")
account.loadCredentials(context) account.loadCredentials(context)
// if mAccount.getCredentials().length() == 0 --> refresh failed // if mAccount.getCredentials().length() == 0 --> refresh failed
client.credentials = account.credentials client.credentials = account.credentials
@ -201,6 +206,7 @@ class ConnectionValidator(
if (!credentialsWereRefreshed) { if (!credentialsWereRefreshed) {
// if credentials are not refreshed, client must be removed // if credentials are not refreshed, client must be removed
// from the OwnCloudClientManager to prevent it is reused once and again // from the OwnCloudClientManager to prevent it is reused once and again
Timber.w("Credentials were not refreshed, client will be removed from the Session Manager to prevent using it over and over")
singleSessionManager.removeClientFor(account) singleSessionManager.removeClientFor(account)
} }
} }
@ -210,6 +216,6 @@ class ConnectionValidator(
} }
companion object { companion object {
private val VALIDATION_RETRY_COUNT = 3 private const val VALIDATION_RETRY_COUNT = 3
} }
} }

View File

@ -43,6 +43,7 @@ import timber.log.Timber;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Locale;
import static com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER; import static com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER;
import static com.owncloud.android.lib.common.http.HttpConstants.HTTP_MOVED_PERMANENTLY; import static com.owncloud.android.lib.common.http.HttpConstants.HTTP_MOVED_PERMANENTLY;
@ -128,6 +129,7 @@ public class OwnCloudClient extends HttpClient {
Timber.d("Executing in request with id %s", requestId); Timber.d("Executing in request with id %s", requestId);
method.setRequestHeader(HttpConstants.OC_X_REQUEST_ID, requestId); method.setRequestHeader(HttpConstants.OC_X_REQUEST_ID, requestId);
method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent()); method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent());
method.setRequestHeader(HttpConstants.ACCEPT_LANGUAGE_HEADER, Locale.getDefault().getLanguage());
method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY); method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY);
if (mCredentials.getHeaderAuth() != null && !mCredentials.getHeaderAuth().isEmpty()) { if (mCredentials.getHeaderAuth() != null && !mCredentials.getHeaderAuth().isEmpty()) {
method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth()); method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth());

View File

@ -31,7 +31,6 @@ import android.net.Uri;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.http.HttpClient;
import timber.log.Timber; import timber.log.Timber;
import java.io.IOException; import java.io.IOException;
@ -124,6 +123,24 @@ public class SingleSessionManager {
} }
} else { } else {
Timber.v("reusing client for account %s", accountName); Timber.v("reusing client for account %s", accountName);
if (client.getAccount() != null &&
client.getAccount().getCredentials() != null &&
(client.getAccount().getCredentials().getAuthToken() == null || client.getAccount().getCredentials().getAuthToken().isEmpty())
) {
Timber.i("Client " + client.getAccount().getName() + " needs to refresh credentials");
//the next two lines are a hack because okHttpclient is used as a singleton instead of being an
//injected instance that can be deleted when required
client.clearCookies();
client.clearCredentials();
client.setAccount(account);
account.loadCredentials(context);
client.setCredentials(account.getCredentials());
Timber.i("Client " + account.getName() + " with credentials size" + client.getAccount().getCredentials().getAuthToken().length());
}
reusingKnown = true; reusingKnown = true;
} }

View File

@ -112,6 +112,7 @@ public class AccountUtils {
String username = AccountUtils.getUsernameForAccount(account); String username = AccountUtils.getUsernameForAccount(account);
if (isOauth2) { if (isOauth2) {
Timber.i("Trying to retrieve credentials for oAuth account" + account.name);
String accessToken = am.blockingGetAuthToken( String accessToken = am.blockingGetAuthToken(
account, account,
AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), AccountTypeUtils.getAuthTokenTypeAccessToken(account.type),

View File

@ -40,6 +40,7 @@ public class HttpConstants {
public static final String IF_MATCH_HEADER = "If-Match"; public static final String IF_MATCH_HEADER = "If-Match";
public static final String IF_NONE_MATCH_HEADER = "If-None-Match"; public static final String IF_NONE_MATCH_HEADER = "If-None-Match";
public static final String CONTENT_TYPE_HEADER = "Content-Type"; public static final String CONTENT_TYPE_HEADER = "Content-Type";
public static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
public static final String CONTENT_LENGTH_HEADER = "Content-Length"; public static final String CONTENT_LENGTH_HEADER = "Content-Length";
public static final String OC_TOTAL_LENGTH_HEADER = "OC-Total-Length"; public static final String OC_TOTAL_LENGTH_HEADER = "OC-Total-Length";
public static final String OC_X_OC_MTIME_HEADER = "X-OC-Mtime"; public static final String OC_X_OC_MTIME_HEADER = "X-OC-Mtime";

View File

@ -51,7 +51,7 @@ class LogInterceptor : Interceptor {
val request = chain.request().also { val request = chain.request().also {
val requestId = it.headers[OC_X_REQUEST_ID] val requestId = it.headers[OC_X_REQUEST_ID]
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}") logHttp(REQUEST, INFO, requestId, "Method: ${it.method} URL: ${it.url}")
logHeaders(requestId, it.headers, REQUEST) logHeaders(requestId, it.headers, REQUEST)
logRequestBody(requestId, it.body) logRequestBody(requestId, it.body)
} }
@ -64,7 +64,7 @@ class LogInterceptor : Interceptor {
RESPONSE, RESPONSE,
INFO, INFO,
requestId, requestId,
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}" "Method: ${request.method} URL: ${request.url} Code: ${it.code} Message: ${it.message}"
) )
logHeaders(requestId, it.headers, RESPONSE) logHeaders(requestId, it.headers, RESPONSE)
logResponseBody(requestId, it.body) logResponseBody(requestId, it.body)

View File

@ -24,13 +24,36 @@
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.Property import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.PropertyUtils.getAllPropSet
import at.bitfire.dav4jvm.PropertyUtils.getQuotaPropset import at.bitfire.dav4jvm.PropertyUtils.getQuotaPropset
import at.bitfire.dav4jvm.property.CreationDate
import at.bitfire.dav4jvm.property.DisplayName
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.ResourceType
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
object DavUtils { object DavUtils {
@JvmStatic val allPropset: Array<Property.Name> @JvmStatic val allPropSet: Array<Property.Name>
get() = getAllPropSet().plus(OCShareTypes.NAME) get() = arrayOf(
DisplayName.NAME,
GetContentType.NAME,
ResourceType.NAME,
GetContentLength.NAME,
GetLastModified.NAME,
CreationDate.NAME,
GetETag.NAME,
OCPermissions.NAME,
OCId.NAME,
OCSize.NAME,
OCPrivatelink.NAME,
OCShareTypes.NAME,
)
val quotaPropSet: Array<Property.Name> val quotaPropSet: Array<Property.Name>
get() = getQuotaPropset() get() = getQuotaPropset()

View File

@ -46,6 +46,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -166,7 +167,10 @@ public class RemoteOperationResult<T>
} else if (e instanceof FileNotFoundException) { } else if (e instanceof FileNotFoundException) {
mCode = ResultCode.LOCAL_FILE_NOT_FOUND; mCode = ResultCode.LOCAL_FILE_NOT_FOUND;
} else { } else if (e instanceof ProtocolException) {
mCode = ResultCode.NETWORK_ERROR;
}
else {
mCode = ResultCode.UNKNOWN_ERROR; mCode = ResultCode.UNKNOWN_ERROR;
} }
} }
@ -589,5 +593,6 @@ public class RemoteOperationResult<T>
SPECIFIC_METHOD_NOT_ALLOWED, SPECIFIC_METHOD_NOT_ALLOWED,
SPECIFIC_BAD_REQUEST, SPECIFIC_BAD_REQUEST,
TOO_EARLY, TOO_EARLY,
NETWORK_ERROR,
} }
} }

View File

@ -0,0 +1,108 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 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.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.network.WebdavUtils
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.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.FormBody
import okhttp3.RequestBody
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
class CreateRemoteFileWithAppProviderOperation(
private val createFileWithAppProviderEndpoint: String,
private val parentContainerId: String,
private val filename: String,
) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try {
val createFileWithAppProviderRequestBody = CreateFileWithAppProviderParams(parentContainerId, filename)
.toRequestBody()
val stringUrl = client.baseUri.toString() + WebdavUtils.encodePath(createFileWithAppProviderEndpoint)
val postMethod = PostMethod(URL(stringUrl), createFileWithAppProviderRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(postMethod)
Timber.d("Create file $filename with app provider in folder with ID $parentContainerId - $status${if (!isSuccess(status)) "(FAIL)" else ""}")
if (isSuccess(status)) RemoteOperationResult<String>(ResultCode.OK).apply {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<CreateFileWithAppProviderResponse> = moshi.adapter(CreateFileWithAppProviderResponse::class.java)
data = postMethod.getResponseBodyAsString()?.let { adapter.fromJson(it)!!.fileId }
}
else RemoteOperationResult<String>(postMethod).apply { data = "" }
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
Timber.e(e, "Create file $filename with app provider in folder with ID $parentContainerId failed")
result
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class CreateFileWithAppProviderParams(
val parentContainerId: String,
val filename: String,
) {
fun toRequestBody(): RequestBody =
FormBody.Builder()
.add(PARAM_PARENT_CONTAINER_ID, parentContainerId)
.add(PARAM_FILENAME, filename)
.build()
companion object {
const val PARAM_PARENT_CONTAINER_ID = "parent_container_id"
const val PARAM_FILENAME = "filename"
}
}
@JsonClass(generateAdapter = true)
data class CreateFileWithAppProviderResponse(
@Json(name = "file_id")
val fileId: String,
)
companion object {
private const val TIMEOUT: Long = 5_000
}
}

View File

@ -0,0 +1,82 @@
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
*
* Copyright (C) 2023 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.appregistry
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 com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import timber.log.Timber
import java.net.URL
class GetRemoteAppRegistryOperation : RemoteOperation<AppRegistryResponse>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<AppRegistryResponse> {
var result: RemoteOperationResult<AppRegistryResponse>
try {
val uriBuilder = client.baseUri.buildUpon().apply {
appendEncodedPath(APP_REGISTRY_ENDPOINT)
}
val getMethod = GetMethod(URL(uriBuilder.build().toString()))
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (status == HttpConstants.HTTP_OK) {
Timber.d("Successful response $response")
// Parse the response
val moshi: Moshi = Moshi.Builder().build()
val adapter: JsonAdapter<AppRegistryResponse> = moshi.adapter(AppRegistryResponse::class.java)
val appRegistryResponse: AppRegistryResponse = response?.let { adapter.fromJson(it) } ?: AppRegistryResponse(value = emptyList())
result = RemoteOperationResult(OK)
result.data = appRegistryResponse
Timber.d("Get AppRegistry completed and parsed to ${result.data}")
} else {
result = RemoteOperationResult(getMethod)
Timber.e("Failed response while getting app registry from the server status code: $status; response message: $response")
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while getting app registry")
}
return result
}
companion object {
private const val APP_REGISTRY_ENDPOINT = "app/list"
}
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -21,7 +21,8 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.resources.files
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
@ -30,7 +31,6 @@ import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.resources.files.GetUrlToOpenInWebRemoteOperation.OpenInWebParams.Companion.PARAM_FILE_ID
import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
@ -41,16 +41,18 @@ import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class GetUrlToOpenInWebRemoteOperation( class GetUrlToOpenInWebRemoteOperation(
val openWithWebEndpoint: String, private val openWithWebEndpoint: String,
val fileId: String, private val fileId: String,
private val appName: String,
) : RemoteOperation<String>() { ) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> { override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try { return try {
val openInWebRequestBody = OpenInWebParams(fileId).toRequestBody() val openInWebRequestBody = OpenInWebParams(fileId, appName).toRequestBody()
val stringUrl = client.baseUri.toString() + WebdavUtils.encodePath(openWithWebEndpoint) + "?$PARAM_FILE_ID=$fileId" val stringUrl =
client.baseUri.toString() + WebdavUtils.encodePath(openWithWebEndpoint)
val postMethod = PostMethod(URL(stringUrl), openInWebRequestBody).apply { val postMethod = PostMethod(URL(stringUrl), openInWebRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS) setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
@ -75,14 +77,21 @@ class GetUrlToOpenInWebRemoteOperation(
} }
} }
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class OpenInWebParams(val fileId: String) { data class OpenInWebParams(
val fileId: String,
val appName: String,
) {
fun toRequestBody(): RequestBody = fun toRequestBody(): RequestBody =
FormBody.Builder().build() FormBody.Builder()
.add(PARAM_FILE_ID, fileId)
.add(PARAM_APP_NAME, appName)
.build()
companion object { companion object {
const val PARAM_FILE_ID = "file_id" const val PARAM_FILE_ID = "file_id"
const val PARAM_APP_NAME = "app_name"
} }
} }

View File

@ -0,0 +1,56 @@
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
*
* Copyright (C) 2023 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.appregistry.responses
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AppRegistryResponse(
@Json(name = "mime-types")
val value: List<AppRegistryMimeTypeResponse>
)
@JsonClass(generateAdapter = true)
data class AppRegistryMimeTypeResponse(
@Json(name = "mime_type") val mimeType: String,
val ext: String? = null,
@Json(name = "app_providers")
val appProviders: List<AppRegistryProviderResponse>,
val name: String? = null,
val icon: String? = null,
val description: String? = null,
@Json(name = "allow_creation")
val allowCreation: Boolean? = null,
@Json(name = "default_application")
val defaultApplication: String? = null
)
@JsonClass(generateAdapter = true)
data class AppRegistryProviderResponse(
val name: String,
val icon: String,
)

View File

@ -0,0 +1,44 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 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.appregistry.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
interface AppRegistryService : Service {
fun getAppRegistry(): RemoteOperationResult<AppRegistryResponse>
fun getUrlToOpenInWeb(
openWebEndpoint: String,
fileId: String,
appName: String,
): RemoteOperationResult<String>
fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String,
): RemoteOperationResult<String>
}

View File

@ -0,0 +1,54 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 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.appregistry.services
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.appregistry.CreateRemoteFileWithAppProviderOperation
import com.owncloud.android.lib.resources.appregistry.GetRemoteAppRegistryOperation
import com.owncloud.android.lib.resources.appregistry.GetUrlToOpenInWebRemoteOperation
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
class OCAppRegistryService(override val client: OwnCloudClient) : AppRegistryService {
override fun getAppRegistry(): RemoteOperationResult<AppRegistryResponse> =
GetRemoteAppRegistryOperation().execute(client)
override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String, appName: String): RemoteOperationResult<String> =
GetUrlToOpenInWebRemoteOperation(
openWithWebEndpoint = openWebEndpoint,
fileId = fileId,
appName = appName
).execute(client)
override fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String
): RemoteOperationResult<String> =
CreateRemoteFileWithAppProviderOperation(
createFileWithAppProviderEndpoint = createFileWithAppProviderEndpoint,
parentContainerId = parentContainerId,
filename = filename,
).execute(client)
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,7 +25,7 @@ package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropset import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropSet
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.network.WebdavUtils import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Abel García de Prada * @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* *
* @param remotePath Path to append to the URL owned by the client instance. * @param remotePath Path to append to the URL owned by the client instance.
* @param isUserLoggedIn When `true`, the username won't be added at the end of the PROPFIND url since is not * @param isUserLoggedIn When `true`, the username won't be added at the end of the PROPFIND url since is not
@ -48,21 +49,22 @@ import java.util.concurrent.TimeUnit
*/ */
class CheckPathExistenceRemoteOperation( class CheckPathExistenceRemoteOperation(
val remotePath: String? = "", val remotePath: String? = "",
val isUserLoggedIn: Boolean val isUserLoggedIn: Boolean,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Boolean>() { ) : RemoteOperation<Boolean>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> { override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> {
return try { val baseStringUrl = spaceWebDavUrl ?: if (isUserLoggedIn) client.baseFilesWebDavUri.toString()
val stringUrl = else client.userFilesWebDavUri.toString()
if (isUserLoggedIn) client.baseFilesWebDavUri.toString() val stringUrl = baseStringUrl + WebdavUtils.encodePath(remotePath)
else client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropset).apply { return try {
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropSet).apply {
setReadTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS) setReadTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS) setConnectionTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
} }
var status = client.executeHttpMethod(propFindMethod) val status = client.executeHttpMethod(propFindMethod)
/* PROPFIND method /* PROPFIND method
* 404 NOT FOUND: path doesn't exist, * 404 NOT FOUND: path doesn't exist,
* 207 MULTI_STATUS: path exists. * 207 MULTI_STATUS: path exists.
@ -77,7 +79,7 @@ class CheckPathExistenceRemoteOperation(
val result = RemoteOperationResult<Boolean>(e) val result = RemoteOperationResult<Boolean>(e)
Timber.e( Timber.e(
e, e,
"Existence check for ${client.userFilesWebDavUri}${WebdavUtils.encodePath(remotePath)} : ${result.logMessage}" "Existence check for $stringUrl : ${result.logMessage}"
) )
result result
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -44,25 +44,29 @@ import java.util.concurrent.TimeUnit
* @author David A. Velasco * @author David A. Velasco
* @author Christian Schabesberger * @author Christian Schabesberger
* @author David González V. * @author David González V.
* @author Juan Carlos Garrote Gascón
* *
* @param srcRemotePath Remote path of the file/folder to copy. * @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it. * @param targetRemotePath Remote path desired for the file/folder to copy it.
*/ */
class CopyRemoteFileOperation( class CopyRemoteFileOperation(
private val srcRemotePath: String, private val sourceRemotePath: String,
private val targetRemotePath: String, private val targetRemotePath: String,
private val sourceSpaceWebDavUrl: String? = null,
private val targetSpaceWebDavUrl: String? = null,
) : RemoteOperation<String>() { ) : RemoteOperation<String>() {
/** /**
* Performs the rename operation. * Performs the rename operation.
* *
* @param client Client object to communicate with the remote ownCloud server. * @param client Client object to communicate with the remote ownCloud server.
*/ */
override fun run(client: OwnCloudClient): RemoteOperationResult<String> { override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
if (targetRemotePath == srcRemotePath) { if (targetRemotePath == sourceRemotePath && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
// nothing to do! // nothing to do!
return RemoteOperationResult(ResultCode.OK) return RemoteOperationResult(ResultCode.OK)
} }
if (targetRemotePath.startsWith(srcRemotePath)) { if (targetRemotePath.startsWith(sourceRemotePath) && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
return RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT) return RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT)
} }
@ -70,8 +74,8 @@ class CopyRemoteFileOperation(
var result: RemoteOperationResult<String> var result: RemoteOperationResult<String>
try { try {
val copyMethod = CopyMethod( val copyMethod = CopyMethod(
URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(srcRemotePath)), URL((sourceSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(targetRemotePath), (targetSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
).apply { ).apply {
setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS) setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
@ -95,10 +99,10 @@ class CopyRemoteFileOperation(
client.exhaustResponse(copyMethod.getResponseBodyAsStream()) client.exhaustResponse(copyMethod.getResponseBodyAsStream())
} }
} }
Timber.i("Copy $srcRemotePath to $targetRemotePath: ${result.logMessage}") Timber.i("Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} catch (e: Exception) { } catch (e: Exception) {
result = RemoteOperationResult(e) result = RemoteOperationResult(e)
Timber.e(e, "Copy $srcRemotePath to $targetRemotePath: ${result.logMessage}") Timber.e(e, "Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} }
return result return result
} }

View File

@ -46,7 +46,8 @@ import java.util.concurrent.TimeUnit
class CreateRemoteFolderOperation( class CreateRemoteFolderOperation(
val remotePath: String, val remotePath: String,
private val createFullPath: Boolean, private val createFullPath: Boolean,
private val isChunksFolder: Boolean = false private val isChunksFolder: Boolean = false,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
@ -67,13 +68,13 @@ class CreateRemoteFolderOperation(
var result: RemoteOperationResult<Unit> var result: RemoteOperationResult<Unit>
try { try {
val webDavUri = if (isChunksFolder) { val webDavUri = if (isChunksFolder) {
client.uploadsWebDavUri client.uploadsWebDavUri.toString()
} else { } else {
client.userFilesWebDavUri spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
} }
val mkCol = MkColMethod( val mkCol = MkColMethod(
URL(webDavUri.toString() + WebdavUtils.encodePath(remotePath)) URL(webDavUri + WebdavUtils.encodePath(remotePath))
).apply { ).apply {
setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS) setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS) setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)

View File

@ -36,7 +36,6 @@ import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URL import java.net.URL
import java.util.HashSet
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
/** /**
@ -47,7 +46,8 @@ import java.util.concurrent.atomic.AtomicBoolean
*/ */
class DownloadRemoteFileOperation( class DownloadRemoteFileOperation(
private val remotePath: String, private val remotePath: String,
localFolderPath: String localFolderPath: String,
private val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
private val cancellationRequested = AtomicBoolean(false) private val cancellationRequested = AtomicBoolean(false)
@ -84,7 +84,8 @@ class DownloadRemoteFileOperation(
var bis: BufferedInputStream? = null var bis: BufferedInputStream? = null
var savedFile = false var savedFile = false
val getMethod = GetMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath))) val webDavUri = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
val getMethod = GetMethod(URL(webDavUri + WebdavUtils.encodePath(remotePath)))
try { try {
val status = client.executeHttpMethod(getMethod) val status = client.executeHttpMethod(getMethod)

View File

@ -44,7 +44,7 @@ class GetBaseUrlRemoteOperation : RemoteOperation<String?>() {
return try { return try {
val stringUrl = client.baseFilesWebDavUri.toString() val stringUrl = client.baseFilesWebDavUri.toString()
val propFindMethod = PropfindMethod(URL(stringUrl), 0, DavUtils.allPropset).apply { val propFindMethod = PropfindMethod(URL(stringUrl), 0, DavUtils.allPropSet).apply {
setReadTimeout(TIMEOUT, TimeUnit.SECONDS) setReadTimeout(TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.SECONDS) setConnectionTimeout(TIMEOUT, TimeUnit.SECONDS)
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -38,17 +38,22 @@ import java.util.concurrent.TimeUnit
/** /**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder * Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account. * in the same account and space.
* *
* Allows renaming the moving file/folder at the same time. * Allows renaming the moving file/folder at the same time.
* *
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Abel García de Prada * @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
*
* @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it.
*/ */
open class MoveRemoteFileOperation( open class MoveRemoteFileOperation(
private val sourceRemotePath: String, private val sourceRemotePath: String,
private val targetRemotePath: String, private val targetRemotePath: String,
private val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
/** /**
@ -73,8 +78,8 @@ open class MoveRemoteFileOperation(
// so this uri has to be customizable // so this uri has to be customizable
val srcWebDavUri = getSrcWebDavUriForClient(client) val srcWebDavUri = getSrcWebDavUriForClient(client)
val moveMethod = MoveMethod( val moveMethod = MoveMethod(
url = URL(srcWebDavUri.toString() + WebdavUtils.encodePath(sourceRemotePath)), url = URL((spaceWebDavUrl ?: srcWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
destinationUrl = client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(targetRemotePath), destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
).apply { ).apply {
addRequestHeaders(this) addRequestHeaders(this)
setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS) setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS)

View File

@ -46,7 +46,10 @@ import java.util.concurrent.TimeUnit
* @author David González Verdugo * @author David González Verdugo
*/ */
class ReadRemoteFileOperation(val remotePath: String) : RemoteOperation<RemoteFile>() { class ReadRemoteFileOperation(
val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<RemoteFile>() {
/** /**
* Performs the read operation. * Performs the read operation.
@ -57,9 +60,9 @@ class ReadRemoteFileOperation(val remotePath: String) : RemoteOperation<RemoteFi
override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteFile> { override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteFile> {
try { try {
val propFind = PropfindMethod( val propFind = PropfindMethod(
url = URL("${client.userFilesWebDavUri}${WebdavUtils.encodePath(remotePath)}"), url = getFinalWebDavUrl(),
depth = DEPTH_0, depth = DEPTH_0,
propertiesToRequest = DavUtils.allPropset propertiesToRequest = DavUtils.allPropSet
).apply { ).apply {
setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS) setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS) setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
@ -69,10 +72,11 @@ class ReadRemoteFileOperation(val remotePath: String) : RemoteOperation<RemoteFi
Timber.i("Read remote file $remotePath with status ${propFind.statusCode}") Timber.i("Read remote file $remotePath with status ${propFind.statusCode}")
return if (isSuccess(status)) { return if (isSuccess(status)) {
// TODO: Remove that !!
val remoteFile = RemoteFile.getRemoteFileFromDav( val remoteFile = RemoteFile.getRemoteFileFromDav(
propFind.root!!, davResource = propFind.root!!,
AccountUtils.getUserId(mAccount, mContext), mAccount.name userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
) )
RemoteOperationResult<RemoteFile>(RemoteOperationResult.ResultCode.OK).apply { RemoteOperationResult<RemoteFile>(RemoteOperationResult.ResultCode.OK).apply {
@ -88,6 +92,12 @@ class ReadRemoteFileOperation(val remotePath: String) : RemoteOperation<RemoteFi
} }
} }
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int) = status.isOneOf(HTTP_MULTI_STATUS, HTTP_OK) private fun isSuccess(status: Int) = status.isOneOf(HTTP_MULTI_STATUS, HTTP_OK)
companion object { companion object {

View File

@ -48,7 +48,8 @@ import java.net.URL
* @author David González Verdugo * @author David González Verdugo
*/ */
class ReadRemoteFolderOperation( class ReadRemoteFolderOperation(
val remotePath: String val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<ArrayList<RemoteFile>>() { ) : RemoteOperation<ArrayList<RemoteFile>>() {
/** /**
@ -61,9 +62,9 @@ class ReadRemoteFolderOperation(
PropertyRegistry.register(OCShareTypes.Factory()) PropertyRegistry.register(OCShareTypes.Factory())
val propfindMethod = PropfindMethod( val propfindMethod = PropfindMethod(
URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)), getFinalWebDavUrl(),
DavConstants.DEPTH_1, DavConstants.DEPTH_1,
DavUtils.allPropset DavUtils.allPropSet
) )
val status = client.executeHttpMethod(propfindMethod) val status = client.executeHttpMethod(propfindMethod)
@ -71,12 +72,11 @@ class ReadRemoteFolderOperation(
if (isSuccess(status)) { if (isSuccess(status)) {
val mFolderAndFiles = ArrayList<RemoteFile>() val mFolderAndFiles = ArrayList<RemoteFile>()
// parse data from remote folder
// TODO: Remove that !!
val remoteFolder = RemoteFile.getRemoteFileFromDav( val remoteFolder = RemoteFile.getRemoteFileFromDav(
davResource = propfindMethod.root!!, davResource = propfindMethod.root!!,
userId = AccountUtils.getUserId(mAccount, mContext), userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
) )
mFolderAndFiles.add(remoteFolder) mFolderAndFiles.add(remoteFolder)
@ -85,7 +85,8 @@ class ReadRemoteFolderOperation(
val remoteFile = RemoteFile.getRemoteFileFromDav( val remoteFile = RemoteFile.getRemoteFileFromDav(
davResource = resource, davResource = resource,
userId = AccountUtils.getUserId(mAccount, mContext), userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
) )
mFolderAndFiles.add(remoteFile) mFolderAndFiles.add(remoteFile)
} }
@ -107,5 +108,11 @@ class ReadRemoteFolderOperation(
} }
} }
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS) private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS)
} }

View File

@ -26,6 +26,7 @@ package com.owncloud.android.lib.resources.files
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import at.bitfire.dav4jvm.PropStat import at.bitfire.dav4jvm.PropStat
import at.bitfire.dav4jvm.Property import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.Response
@ -38,8 +39,6 @@ import at.bitfire.dav4jvm.property.OCId
import at.bitfire.dav4jvm.property.OCPermissions import at.bitfire.dav4jvm.property.OCPermissions
import at.bitfire.dav4jvm.property.OCPrivatelink import at.bitfire.dav4jvm.property.OCPrivatelink
import at.bitfire.dav4jvm.property.OCSize import at.bitfire.dav4jvm.property.OCSize
import at.bitfire.dav4jvm.property.QuotaAvailableBytes
import at.bitfire.dav4jvm.property.QuotaUsedBytes
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
@ -50,7 +49,6 @@ import kotlinx.parcelize.Parcelize
import okhttp3.HttpUrl import okhttp3.HttpUrl
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.math.BigDecimal
/** /**
* Contains the data of a Remote File from a WebDavEntry * Contains the data of a Remote File from a WebDavEntry
@ -72,8 +70,6 @@ data class RemoteFile(
var permissions: String? = null, var permissions: String? = null,
var remoteId: String? = null, var remoteId: String? = null,
var size: Long = 0, var size: Long = 0,
var quotaUsedBytes: BigDecimal? = null,
var quotaAvailableBytes: BigDecimal? = null,
var privateLink: String? = null, var privateLink: String? = null,
var owner: String, var owner: String,
var sharedByLink: Boolean = false, var sharedByLink: Boolean = false,
@ -100,8 +96,13 @@ data class RemoteFile(
const val MIME_DIR = "DIR" const val MIME_DIR = "DIR"
const val MIME_DIR_UNIX = "httpd/unix-directory" const val MIME_DIR_UNIX = "httpd/unix-directory"
fun getRemoteFileFromDav(davResource: Response, userId: String, userName: String): RemoteFile { fun getRemoteFileFromDav(
val remotePath = getRemotePathFromUrl(davResource.href, userId) davResource: Response,
userId: String,
userName: String,
spaceWebDavUrl: String? = null
): RemoteFile {
val remotePath = getRemotePathFromUrl(davResource.href, userId, spaceWebDavUrl)
val remoteFile = RemoteFile(remotePath = remotePath, owner = userName) val remoteFile = RemoteFile(remotePath = remotePath, owner = userName)
val properties = getPropertiesEvenIfPostProcessing(davResource) val properties = getPropertiesEvenIfPostProcessing(davResource)
@ -131,12 +132,6 @@ data class RemoteFile(
is OCSize -> { is OCSize -> {
remoteFile.size = property.size remoteFile.size = property.size
} }
is QuotaUsedBytes -> {
remoteFile.quotaUsedBytes = BigDecimal.valueOf(property.quotaUsedBytes)
}
is QuotaAvailableBytes -> {
remoteFile.quotaAvailableBytes = BigDecimal.valueOf(property.quotaAvailableBytes)
}
is OCPrivatelink -> { is OCPrivatelink -> {
remoteFile.privateLink = property.link remoteFile.privateLink = property.link
} }
@ -164,15 +159,25 @@ data class RemoteFile(
* Retrieves a relative path from a remote file url * Retrieves a relative path from a remote file url
* *
* *
* Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt * Example legacy:
* /remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt
*
* Example spaces:
* /dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9/Documents/text.txt => /Documents/text.txt
* *
* @param url remote file url * @param url remote file url
* @param userId file owner * @param userId file owner
* @param spaceWebDavUrl custom web dav url for space
* @return remote relative path of the file * @return remote relative path of the file
*/ */
private fun getRemotePathFromUrl(url: HttpUrl, userId: String): String { @VisibleForTesting
val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId fun getRemotePathFromUrl(
val absoluteDavPath = Uri.decode(url.encodedPath) url: HttpUrl,
userId: String,
spaceWebDavUrl: String? = null,
): String {
val davFilesPath = spaceWebDavUrl ?: (OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId)
val absoluteDavPath = if (spaceWebDavUrl != null) Uri.decode(url.toString()) else Uri.decode(url.encodedPath)
val pathToOc = absoluteDavPath.split(davFilesPath).first() val pathToOc = absoluteDavPath.split(davFilesPath).first()
return absoluteDavPath.replace(pathToOc + davFilesPath, "") return absoluteDavPath.replace(pathToOc + davFilesPath, "")
} }

View File

@ -24,7 +24,6 @@
package com.owncloud.android.lib.resources.files package com.owncloud.android.lib.resources.files
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_NO_CONTENT import com.owncloud.android.lib.common.http.HttpConstants.HTTP_NO_CONTENT
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
@ -46,7 +45,8 @@ import java.net.URL
* @author Abel García de Prada * @author Abel García de Prada
*/ */
open class RemoveRemoteFileOperation( open class RemoveRemoteFileOperation(
private val remotePath: String private val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
@ -54,7 +54,7 @@ open class RemoveRemoteFileOperation(
try { try {
val srcWebDavUri = getSrcWebDavUriForClient(client) val srcWebDavUri = getSrcWebDavUriForClient(client)
val deleteMethod = DeleteMethod( val deleteMethod = DeleteMethod(
URL(srcWebDavUri.toString() + WebdavUtils.encodePath(remotePath)) URL(srcWebDavUri + WebdavUtils.encodePath(remotePath))
) )
val status = client.executeHttpMethod(deleteMethod) val status = client.executeHttpMethod(deleteMethod)
@ -75,7 +75,7 @@ open class RemoveRemoteFileOperation(
* For standard removals, we will use [OwnCloudClient.getUserFilesWebDavUri]. * For standard removals, we will use [OwnCloudClient.getUserFilesWebDavUri].
* In case we need a different source Uri, override this method. * In case we need a different source Uri, override this method.
*/ */
open fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.userFilesWebDavUri open fun getSrcWebDavUriForClient(client: OwnCloudClient): String = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
private fun isSuccess(status: Int) = status.isOneOf(HTTP_OK, HTTP_NO_CONTENT) private fun isSuccess(status: Int) = status.isOneOf(HTTP_OK, HTTP_NO_CONTENT)
} }

View File

@ -48,6 +48,7 @@ class RenameRemoteFileOperation(
private val oldRemotePath: String, private val oldRemotePath: String,
private val newName: String, private val newName: String,
isFolder: Boolean, isFolder: Boolean,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
private var newRemotePath: String private var newRemotePath: String
@ -75,8 +76,8 @@ class RenameRemoteFileOperation(
} }
val moveMethod: MoveMethod = MoveMethod( val moveMethod: MoveMethod = MoveMethod(
url = URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(oldRemotePath)), url = URL((spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(oldRemotePath)),
destinationUrl = client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(newRemotePath), destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(newRemotePath),
).apply { ).apply {
setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.MILLISECONDS) setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(RENAME_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) setConnectionTimeout(RENAME_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -48,6 +48,7 @@ import java.util.concurrent.atomic.AtomicBoolean
* @author masensio * @author masensio
* @author David González Verdugo * @author David González Verdugo
* @author Abel García de Prada * @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
*/ */
open class UploadFileFromFileSystemOperation( open class UploadFileFromFileSystemOperation(
val localPath: String, val localPath: String,
@ -55,6 +56,7 @@ open class UploadFileFromFileSystemOperation(
val mimeType: String, val mimeType: String,
val lastModifiedTimestamp: String, val lastModifiedTimestamp: String,
val requiredEtag: String?, val requiredEtag: String?,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() { ) : RemoteOperation<Unit>() {
protected val cancellationRequested = AtomicBoolean(false) protected val cancellationRequested = AtomicBoolean(false)
@ -97,7 +99,8 @@ open class UploadFileFromFileSystemOperation(
synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) } synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) }
} }
putMethod = PutMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)), fileRequestBody!!).apply { val baseStringUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
putMethod = PutMethod(URL(baseStringUrl + WebdavUtils.encodePath(remotePath)), fileRequestBody!!).apply {
retryOnConnectionFailure = false retryOnConnectionFailure = false
if (!requiredEtag.isNullOrBlank()) { if (!requiredEtag.isNullOrBlank()) {
addRequestHeader(HttpConstants.IF_MATCH_HEADER, requiredEtag) addRequestHeader(HttpConstants.IF_MATCH_HEADER, requiredEtag)

View File

@ -24,10 +24,9 @@
*/ */
package com.owncloud.android.lib.resources.files.chunks package com.owncloud.android.lib.resources.files.chunks
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation
class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) { class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) {
override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri override fun getSrcWebDavUriForClient(client: OwnCloudClient): String = client.uploadsWebDavUri.toString()
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -28,22 +28,24 @@ import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.files.RemoteFile import com.owncloud.android.lib.resources.files.RemoteFile
interface FileService : Service { interface FileService : Service {
fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String>
fun checkPathExistence( fun checkPathExistence(
path: String, path: String,
isUserLogged: Boolean isUserLogged: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Boolean> ): RemoteOperationResult<Boolean>
fun copyFile( fun copyFile(
sourceRemotePath: String, sourceRemotePath: String,
targetRemotePath: String, targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
): RemoteOperationResult<String> ): RemoteOperationResult<String>
fun createFolder( fun createFolder(
remotePath: String, remotePath: String,
createFullPath: Boolean, createFullPath: Boolean,
isChunkFolder: Boolean = false isChunkFolder: Boolean = false,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit> ): RemoteOperationResult<Unit>
fun downloadFile( fun downloadFile(
@ -54,18 +56,22 @@ interface FileService : Service {
fun moveFile( fun moveFile(
sourceRemotePath: String, sourceRemotePath: String,
targetRemotePath: String, targetRemotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> ): RemoteOperationResult<Unit>
fun readFile( fun readFile(
remotePath: String remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<RemoteFile> ): RemoteOperationResult<RemoteFile>
fun refreshFolder( fun refreshFolder(
remotePath: String remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<ArrayList<RemoteFile>> ): RemoteOperationResult<ArrayList<RemoteFile>>
fun removeFile( fun removeFile(
remotePath: String remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit> ): RemoteOperationResult<Unit>
fun renameFile( fun renameFile(
@ -73,5 +79,6 @@ interface FileService : Service {
oldRemotePath: String, oldRemotePath: String,
newName: String, newName: String,
isFolder: Boolean, isFolder: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit> ): RemoteOperationResult<Unit>
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -29,7 +29,6 @@ import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperatio
import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation
import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation
import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation
import com.owncloud.android.lib.resources.files.GetUrlToOpenInWebRemoteOperation
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation
@ -39,37 +38,41 @@ import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation
import com.owncloud.android.lib.resources.files.services.FileService import com.owncloud.android.lib.resources.files.services.FileService
class OCFileService(override val client: OwnCloudClient) : FileService { class OCFileService(override val client: OwnCloudClient) : FileService {
override fun checkPathExistence( override fun checkPathExistence(
path: String, path: String,
isUserLogged: Boolean isUserLogged: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Boolean> = ): RemoteOperationResult<Boolean> =
CheckPathExistenceRemoteOperation( CheckPathExistenceRemoteOperation(
remotePath = path, remotePath = path,
isUserLoggedIn = isUserLogged isUserLoggedIn = isUserLogged,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> =
GetUrlToOpenInWebRemoteOperation(openWithWebEndpoint = openWebEndpoint, fileId = fileId).execute(client)
override fun copyFile( override fun copyFile(
sourceRemotePath: String, sourceRemotePath: String,
targetRemotePath: String targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
): RemoteOperationResult<String> = ): RemoteOperationResult<String> =
CopyRemoteFileOperation( CopyRemoteFileOperation(
srcRemotePath = sourceRemotePath, sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath targetRemotePath = targetRemotePath,
sourceSpaceWebDavUrl = sourceSpaceWebDavUrl,
targetSpaceWebDavUrl = targetSpaceWebDavUrl,
).execute(client) ).execute(client)
override fun createFolder( override fun createFolder(
remotePath: String, remotePath: String,
createFullPath: Boolean, createFullPath: Boolean,
isChunkFolder: Boolean isChunkFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> = ): RemoteOperationResult<Unit> =
CreateRemoteFolderOperation( CreateRemoteFolderOperation(
remotePath = remotePath, remotePath = remotePath,
createFullPath = createFullPath, createFullPath = createFullPath,
isChunksFolder = isChunkFolder isChunksFolder = isChunkFolder,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun downloadFile( override fun downloadFile(
@ -84,43 +87,53 @@ class OCFileService(override val client: OwnCloudClient) : FileService {
override fun moveFile( override fun moveFile(
sourceRemotePath: String, sourceRemotePath: String,
targetRemotePath: String, targetRemotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> = ): RemoteOperationResult<Unit> =
MoveRemoteFileOperation( MoveRemoteFileOperation(
sourceRemotePath = sourceRemotePath, sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath, targetRemotePath = targetRemotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun readFile( override fun readFile(
remotePath: String remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<RemoteFile> = ): RemoteOperationResult<RemoteFile> =
ReadRemoteFileOperation( ReadRemoteFileOperation(
remotePath = remotePath remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun refreshFolder( override fun refreshFolder(
remotePath: String remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<ArrayList<RemoteFile>> = ): RemoteOperationResult<ArrayList<RemoteFile>> =
ReadRemoteFolderOperation( ReadRemoteFolderOperation(
remotePath = remotePath remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun removeFile( override fun removeFile(
remotePath: String remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> = ): RemoteOperationResult<Unit> =
RemoveRemoteFileOperation( RemoveRemoteFileOperation(
remotePath = remotePath remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
override fun renameFile( override fun renameFile(
oldName: String, oldName: String,
oldRemotePath: String, oldRemotePath: String,
newName: String, newName: String,
isFolder: Boolean isFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> = ): RemoteOperationResult<Unit> =
RenameRemoteFileOperation( RenameRemoteFileOperation(
oldName = oldName, oldName = oldName,
oldRemotePath = oldRemotePath, oldRemotePath = oldRemotePath,
newName = newName, newName = newName,
isFolder = isFolder isFolder = isFolder,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
} }

View File

@ -89,8 +89,6 @@ class CreateRemoteShareOperation(
var expirationDateInMillis: Long = INIT_EXPIRATION_DATE_IN_MILLIS // Expiration date to set for the public link var expirationDateInMillis: Long = INIT_EXPIRATION_DATE_IN_MILLIS // Expiration date to set for the public link
var publicUpload: Boolean = false // Upload permissions for the public link (only folders)
var retrieveShareDetails = false // To retrieve more info about the just created share var retrieveShareDetails = false // To retrieve more info about the just created share
private fun buildRequestUri(baseUri: Uri) = private fun buildRequestUri(baseUri: Uri) =
@ -99,7 +97,7 @@ class CreateRemoteShareOperation(
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT) .appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build() .build()
private fun parseResponse(response: String): ShareResponse? { private fun parseResponse(response: String): ShareResponse {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java) val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType) val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
@ -155,12 +153,10 @@ class CreateRemoteShareOperation(
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate) formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
} }
if (publicUpload) {
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString())
}
if (password.isNotEmpty()) { if (password.isNotEmpty()) {
formBodyBuilder.add(PARAM_PASSWORD, password) formBodyBuilder.add(PARAM_PASSWORD, password)
} }
if (RemoteShare.DEFAULT_PERMISSION != permissions) { if (RemoteShare.DEFAULT_PERMISSION != permissions) {
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString()) formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
} }
@ -207,7 +203,6 @@ class CreateRemoteShareOperation(
private const val PARAM_SHARE_TYPE = "shareType" private const val PARAM_SHARE_TYPE = "shareType"
private const val PARAM_SHARE_WITH = "shareWith" private const val PARAM_SHARE_WITH = "shareWith"
private const val PARAM_PASSWORD = "password" private const val PARAM_PASSWORD = "password"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
private const val PARAM_PERMISSIONS = "permissions" private const val PARAM_PERMISSIONS = "permissions"
//Arguments - constant values //Arguments - constant values

View File

@ -36,13 +36,7 @@ import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL import java.net.URL
/** /**
@ -52,14 +46,10 @@ import java.net.URL
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Fernando Sanz Velasco * @author Fernando Sanz Velasco
*/
/**
* Constructor
* *
* @param remoteShareId Share ID * @param remoteShareId Share ID
*/ */
class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<ShareResponse>() { class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<Unit>() {
private fun buildRequestUri(baseUri: Uri) = private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon() baseUri.buildUpon()
@ -68,24 +58,12 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT) .appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build() .build()
private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val listOfShareItemType: Type = Types.newParameterizedType(List::class.java, ShareItem::class.java)
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, listOfShareItemType)
val adapter: JsonAdapter<CommonOcsResponse<List<ShareItem>>> = moshi.adapter(commonOcsType)
return adapter.fromJson(response)?.ocs?.data?.let { listOfShareItems ->
ShareResponse(listOfShareItems.map { shareItem ->
shareItem.toRemoteShare()
})
}
}
private fun onResultUnsuccessful( private fun onResultUnsuccessful(
method: DeleteMethod, method: DeleteMethod,
response: String?, response: String?,
status: Int status: Int
): RemoteOperationResult<ShareResponse> { ): RemoteOperationResult<Unit> {
Timber.e("Failed response while unshare link ") Timber.e("Failed response while removing share ")
if (response != null) { if (response != null) {
Timber.e("*** status code: $status; response message: $response") Timber.e("*** status code: $status; response message: $response")
} else { } else {
@ -94,17 +72,14 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
return RemoteOperationResult(method) return RemoteOperationResult(method)
} }
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> { private fun onRequestSuccessful(response: String?): RemoteOperationResult<Unit> {
val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK) val result = RemoteOperationResult<Unit>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response") Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Unshare link completed ") Timber.d("*** Unshare link completed ")
return result return result
} }
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> { override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
val requestUri = buildRequestUri(client.baseUri) val requestUri = buildRequestUri(client.baseUri)
val deleteMethod = DeleteMethod(URL(requestUri.toString())).apply { val deleteMethod = DeleteMethod(URL(requestUri.toString())).apply {
@ -129,7 +104,7 @@ class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOper
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object { companion object {
//OCS Route // OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares" private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
} }
} }

View File

@ -103,13 +103,6 @@ class UpdateRemoteShareOperation
*/ */
var permissions: Int = DEFAULT_PERMISSION var permissions: Int = DEFAULT_PERMISSION
/**
* Enable upload permissions to update in Share resource.
*
* Null results in no update applied to the upload permission.
*/
var publicUpload: Boolean? = null
var retrieveShareDetails = false // To retrieve more info about the just updated share var retrieveShareDetails = false // To retrieve more info about the just updated share
private fun buildRequestUri(baseUri: Uri) = private fun buildRequestUri(baseUri: Uri) =
@ -119,7 +112,7 @@ class UpdateRemoteShareOperation
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT) .appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build() .build()
private fun parseResponse(response: String): ShareResponse? { private fun parseResponse(response: String): ShareResponse {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java) val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType) val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
@ -181,10 +174,6 @@ class UpdateRemoteShareOperation
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate) formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
} // else, ignore - no update } // else, ignore - no update
if (publicUpload != null) {
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString())
}
// IMPORTANT: permissions parameter needs to be updated after mPublicUpload parameter, // IMPORTANT: permissions parameter needs to be updated after mPublicUpload parameter,
// otherwise they would be set always as 1 (READ) in the server when mPublicUpload was updated // otherwise they would be set always as 1 (READ) in the server when mPublicUpload was updated
if (permissions > DEFAULT_PERMISSION) { if (permissions > DEFAULT_PERMISSION) {
@ -233,7 +222,6 @@ class UpdateRemoteShareOperation
private const val PARAM_PASSWORD = "password" private const val PARAM_PASSWORD = "password"
private const val PARAM_EXPIRATION_DATE = "expireDate" private const val PARAM_EXPIRATION_DATE = "expireDate"
private const val PARAM_PERMISSIONS = "permissions" private const val PARAM_PERMISSIONS = "permissions"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
//Arguments - constant values //Arguments - constant values
private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd" private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"

View File

@ -46,7 +46,6 @@ interface ShareService : Service {
name: String, name: String,
password: String, password: String,
expirationDate: Long, expirationDate: Long,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> ): RemoteOperationResult<ShareResponse>
fun updateShare( fun updateShare(
@ -55,8 +54,7 @@ interface ShareService : Service {
password: String?, password: String?,
expirationDate: Long, expirationDate: Long,
permissions: Int, permissions: Int,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> ): RemoteOperationResult<ShareResponse>
fun deleteShare(remoteId: String): RemoteOperationResult<ShareResponse> fun deleteShare(remoteId: String): RemoteOperationResult<Unit>
} }

View File

@ -36,8 +36,7 @@ import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation
import com.owncloud.android.lib.resources.shares.services.ShareService import com.owncloud.android.lib.resources.shares.services.ShareService
class OCShareService(override val client: OwnCloudClient) : class OCShareService(override val client: OwnCloudClient) : ShareService {
ShareService {
override fun getShares( override fun getShares(
remoteFilePath: String, remoteFilePath: String,
reshares: Boolean, reshares: Boolean,
@ -56,7 +55,6 @@ class OCShareService(override val client: OwnCloudClient) :
name: String, name: String,
password: String, password: String,
expirationDate: Long, expirationDate: Long,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> = ): RemoteOperationResult<ShareResponse> =
CreateRemoteShareOperation( CreateRemoteShareOperation(
remoteFilePath, remoteFilePath,
@ -67,7 +65,6 @@ class OCShareService(override val client: OwnCloudClient) :
this.name = name this.name = name
this.password = password this.password = password
this.expirationDateInMillis = expirationDate this.expirationDateInMillis = expirationDate
this.publicUpload = publicUpload
this.retrieveShareDetails = true this.retrieveShareDetails = true
}.execute(client) }.execute(client)
@ -77,7 +74,6 @@ class OCShareService(override val client: OwnCloudClient) :
password: String?, password: String?,
expirationDate: Long, expirationDate: Long,
permissions: Int, permissions: Int,
publicUpload: Boolean
): RemoteOperationResult<ShareResponse> = ): RemoteOperationResult<ShareResponse> =
UpdateRemoteShareOperation( UpdateRemoteShareOperation(
remoteId remoteId
@ -86,11 +82,10 @@ class OCShareService(override val client: OwnCloudClient) :
this.password = password this.password = password
this.expirationDateInMillis = expirationDate this.expirationDateInMillis = expirationDate
this.permissions = permissions this.permissions = permissions
this.publicUpload = publicUpload
this.retrieveShareDetails = true this.retrieveShareDetails = true
}.execute(client) }.execute(client)
override fun deleteShare(remoteId: String): RemoteOperationResult<ShareResponse> = override fun deleteShare(remoteId: String): RemoteOperationResult<Unit> =
RemoveRemoteShareOperation( RemoveRemoteShareOperation(
remoteId remoteId
).execute(client) ).execute(client)

View File

@ -0,0 +1,99 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 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.spaces
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.resources.spaces.responses.SpaceResponse
import com.owncloud.android.lib.resources.spaces.responses.SpacesResponseWrapper
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import timber.log.Timber
import java.net.URL
class GetRemoteSpacesOperation : RemoteOperation<List<SpaceResponse>>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<List<SpaceResponse>> {
val requestUri = buildRequestUri(client.baseUri)
val getMethod = GetMethod(URL(requestUri.toString()))
return try {
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (isSuccess(status)) {
onRequestSuccessful(response)
} else {
onResultUnsuccessful(getMethod, response, status)
}
} catch (e: Exception) {
Timber.e(e, "Exception while getting remote shares")
RemoteOperationResult(e)
}
}
private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon()
.appendEncodedPath(GRAPH_API_PATH)
.appendEncodedPath(ENDPOINT_SPACES_LIST)
.build()
private fun parseResponse(response: String): List<SpaceResponse> {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<SpacesResponseWrapper> = moshi.adapter(SpacesResponseWrapper::class.java)
return adapter.fromJson(response)?.value ?: listOf()
}
private fun onResultUnsuccessful(
method: GetMethod,
response: String?,
status: Int
): RemoteOperationResult<List<SpaceResponse>> {
Timber.e("Failed response while getting spaces for user")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
private fun onRequestSuccessful(response: String?): RemoteOperationResult<List<SpaceResponse>> {
val result = RemoteOperationResult<List<SpaceResponse>>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
result.data = response?.let { parseResponse(it) } ?: listOf()
Timber.d("*** Fetch of spaces completed and parsed to ${result.data}")
return result
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
companion object {
private const val GRAPH_API_PATH = "graph/v1.0"
private const val ENDPOINT_SPACES_LIST = "me/drives"
}
}

View File

@ -0,0 +1,98 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 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.spaces.responses
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SpacesResponseWrapper(
val value: List<SpaceResponse>
)
@JsonClass(generateAdapter = true)
data class SpaceResponse(
val description: String?,
val driveAlias: String,
val driveType: String,
val id: String,
val lastModifiedDateTime: String?,
val name: String,
val owner: OwnerResponse?,
val quota: QuotaResponse?,
val root: RootResponse,
val special: List<SpecialResponse>?,
val webUrl: String,
)
@JsonClass(generateAdapter = true)
data class OwnerResponse(
val user: UserResponse
)
@JsonClass(generateAdapter = true)
data class QuotaResponse(
val remaining: Long?,
val state: String?,
val total: Long,
val used: Long?,
)
@JsonClass(generateAdapter = true)
data class RootResponse(
val eTag: String?,
val id: String,
val webDavUrl: String,
val deleted: DeleteResponse?,
)
@JsonClass(generateAdapter = true)
data class SpecialResponse(
val eTag: String,
val file: FileResponse,
val id: String,
val lastModifiedDateTime: String,
val name: String,
val size: Int,
val specialFolder: SpecialFolderResponse,
val webDavUrl: String
)
@JsonClass(generateAdapter = true)
data class UserResponse(
val id: String
)
@JsonClass(generateAdapter = true)
data class FileResponse(
val mimeType: String
)
@JsonClass(generateAdapter = true)
data class DeleteResponse(
val state: String,
)
@JsonClass(generateAdapter = true)
data class SpecialFolderResponse(
val name: String
)

View File

@ -0,0 +1,34 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 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.spaces.services
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.spaces.GetRemoteSpacesOperation
import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse
class OCSpacesService(override val client: OwnCloudClient) : SpacesService {
override fun getSpaces(): RemoteOperationResult<List<SpaceResponse>> {
return GetRemoteSpacesOperation().execute(client)
}
}

View File

@ -0,0 +1,31 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 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.spaces.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse
interface SpacesService : Service {
fun getSpaces(): RemoteOperationResult<List<SpaceResponse>>
}

View File

@ -45,7 +45,7 @@ import java.net.URL
/** /**
* Get the Capabilities from the server * Get the Capabilities from the server
* Save in Result.getData in a RemoteCapability object * Save Result.getData in a RemoteCapability object
* *
* @author masensio * @author masensio
* @author David González Verdugo * @author David González Verdugo

View File

@ -1,29 +1,33 @@
/* ownCloud Android Library is available under MIT license /**
* @author masensio * ownCloud Android Library is available under MIT license
* @author David González Verdugo
* @author Abel García de Prada
* Copyright (C) 2020 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * @author masensio
* of this software and associated documentation files (the "Software"), to deal * @author David González Verdugo
* in the Software without restriction, including without limitation the rights * @author Abel García de Prada
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * @author Juan Carlos Garrote Gascón
* 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 * Copyright (C) 2022 ownCloud GmbH.
* all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * Permission is hereby granted, free of charge, to any person obtaining a copy
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * of this software and associated documentation files (the "Software"), to deal
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * in the Software without restriction, including without limitation the rights
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * copies of the Software, and to permit persons to whom the Software is
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * furnished to do so, subject to the following conditions:
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* *
* 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.status package com.owncloud.android.lib.resources.status
/** /**
@ -33,7 +37,7 @@ data class RemoteCapability(
var accountName: String = "", var accountName: String = "",
// Server version // Server version
var versionMayor: Int = 0, var versionMajor: Int = 0,
var versionMinor: Int = 0, var versionMinor: Int = 0,
var versionMicro: Int = 0, var versionMicro: Int = 0,
var versionString: String = "", var versionString: String = "",
@ -68,7 +72,10 @@ data class RemoteCapability(
var filesUndelete: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN, var filesUndelete: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
var filesVersioning: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN, var filesVersioning: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
val filesPrivateLinks: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN, val filesPrivateLinks: CapabilityBooleanType = CapabilityBooleanType.UNKNOWN,
val filesAppProviders: List<RemoteOCISProvider>?, val filesAppProviders: List<RemoteAppProviders>?,
// Spaces
val spaces: RemoteSpaces?,
) { ) {
/** /**
* Enum for Boolean Type in capabilities, with values: * Enum for Boolean Type in capabilities, with values:
@ -101,7 +108,7 @@ data class RemoteCapability(
} }
} }
data class RemoteOCISProvider( data class RemoteAppProviders(
val enabled: Boolean, val enabled: Boolean,
val version: String, val version: String,
val appsUrl: String?, val appsUrl: String?,
@ -109,4 +116,10 @@ data class RemoteCapability(
val openWebUrl: String?, val openWebUrl: String?,
val newUrl: String?, val newUrl: String?,
) )
data class RemoteSpaces(
val enabled: Boolean,
val projects: Boolean,
val shareJail: Boolean,
)
} }

View File

@ -1,32 +1,35 @@
/* ownCloud Android Library is available under MIT license /**
* @author Abel García de Prada * 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 * @author Abel García de Prada
* of this software and associated documentation files (the "Software"), to deal * @author Juan Carlos Garrote Gascón
* 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 * Copyright (C) 2022 ownCloud GmbH.
* all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * Permission is hereby granted, free of charge, to any person obtaining a copy
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * of this software and associated documentation files (the "Software"), to deal
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * in the Software without restriction, including without limitation the rights
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * copies of the Software, and to permit persons to whom the Software is
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * furnished to do so, subject to the following conditions:
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* *
* 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.status.responses package com.owncloud.android.lib.resources.status.responses
import com.owncloud.android.lib.resources.status.RemoteCapability import com.owncloud.android.lib.resources.status.RemoteCapability
import com.owncloud.android.lib.resources.status.RemoteCapability.CapabilityBooleanType import com.owncloud.android.lib.resources.status.RemoteCapability.*
import com.owncloud.android.lib.resources.status.RemoteCapability.RemoteOCISProvider
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@ -37,7 +40,7 @@ data class CapabilityResponse(
val capabilities: Capabilities? val capabilities: Capabilities?
) { ) {
fun toRemoteCapability(): RemoteCapability = RemoteCapability( fun toRemoteCapability(): RemoteCapability = RemoteCapability(
versionMayor = serverVersion?.major ?: 0, versionMajor = serverVersion?.major ?: 0,
versionMinor = serverVersion?.minor ?: 0, versionMinor = serverVersion?.minor ?: 0,
versionMicro = serverVersion?.micro ?: 0, versionMicro = serverVersion?.micro ?: 0,
versionString = serverVersion?.string ?: "", versionString = serverVersion?.string ?: "",
@ -70,10 +73,11 @@ data class CapabilityResponse(
filesUndelete = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.undelete), filesUndelete = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.undelete),
filesVersioning = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.versioning), filesVersioning = CapabilityBooleanType.fromBooleanValue(capabilities?.fileCapabilities?.versioning),
filesPrivateLinks = capabilities?.fileCapabilities?.privateLinks?.let { CapabilityBooleanType.fromBooleanValue(it) } ?: CapabilityBooleanType.UNKNOWN, filesPrivateLinks = capabilities?.fileCapabilities?.privateLinks?.let { CapabilityBooleanType.fromBooleanValue(it) } ?: CapabilityBooleanType.UNKNOWN,
filesAppProviders = capabilities?.fileCapabilities?.appProviders?.map { it.toOCISProvider() }, filesAppProviders = capabilities?.fileCapabilities?.appProviders?.map { it.toAppProviders() },
filesSharingFederationIncoming = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.incoming), filesSharingFederationIncoming = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.incoming),
filesSharingFederationOutgoing = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.outgoing), filesSharingFederationOutgoing = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingFederation?.outgoing),
filesSharingUserProfilePicture = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingUser?.profilePicture), filesSharingUserProfilePicture = CapabilityBooleanType.fromBooleanValue(capabilities?.fileSharingCapabilities?.fileSharingUser?.profilePicture),
spaces = capabilities?.spacesCapabilities?.toSpaces(),
) )
} }
@ -86,7 +90,9 @@ data class Capabilities(
@Json(name = "files") @Json(name = "files")
val fileCapabilities: FileCapabilities?, val fileCapabilities: FileCapabilities?,
@Json(name = "dav") @Json(name = "dav")
val davCapabilities: DavCapabilities? val davCapabilities: DavCapabilities?,
@Json(name = "spaces")
val spacesCapabilities: SpacesCapabilities?,
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -182,7 +188,7 @@ data class AppProvider(
@Json(name = "new_url") @Json(name = "new_url")
val newUrl: String?, val newUrl: String?,
) { ) {
fun toOCISProvider() = RemoteOCISProvider(enabled, version, appsUrl, openUrl, openWebUrl, newUrl) fun toAppProviders() = RemoteAppProviders(enabled, version, appsUrl, openUrl, openWebUrl, newUrl)
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -190,6 +196,16 @@ data class DavCapabilities(
val chunking: String? val chunking: String?
) )
@JsonClass(generateAdapter = true)
data class SpacesCapabilities(
val enabled: Boolean,
val projects: Boolean,
@Json(name = "share_jail")
val shareJail: Boolean,
) {
fun toSpaces() = RemoteSpaces(enabled, projects, shareJail)
}
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class ServerVersion( data class ServerVersion(
var major: Int?, var major: Int?,

View File

@ -1,8 +1,6 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2022 ownCloud GmbH.
* *
* @author David González Verdugo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -32,8 +30,7 @@ import com.owncloud.android.lib.resources.status.GetRemoteCapabilitiesOperation
import com.owncloud.android.lib.resources.status.RemoteCapability import com.owncloud.android.lib.resources.status.RemoteCapability
import com.owncloud.android.lib.resources.status.services.CapabilityService import com.owncloud.android.lib.resources.status.services.CapabilityService
class OCCapabilityService(override val client: OwnCloudClient) : class OCCapabilityService(override val client: OwnCloudClient) : CapabilityService {
CapabilityService {
override fun getCapabilities(): RemoteOperationResult<RemoteCapability> = override fun getCapabilities(): RemoteOperationResult<RemoteCapability> =
GetRemoteCapabilitiesOperation().execute(client) GetRemoteCapabilitiesOperation().execute(client)
} }

View File

@ -1,8 +1,6 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH. * Copyright (C) 2022 ownCloud GmbH.
* *
* @author Abel García de Prada
*
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -26,7 +24,6 @@
package com.owncloud.android.lib.resources.status.services.implementation package com.owncloud.android.lib.resources.status.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
@ -39,16 +36,16 @@ class OCServerInfoService : ServerInfoService {
override fun checkPathExistence( override fun checkPathExistence(
path: String, path: String,
isUserLoggedIn: Boolean, isUserLoggedIn: Boolean,
client: OwnCloudClient client: OwnCloudClient,
): RemoteOperationResult<Boolean> = ): RemoteOperationResult<Boolean> =
CheckPathExistenceRemoteOperation( CheckPathExistenceRemoteOperation(
remotePath = path, remotePath = path,
isUserLoggedIn = true isUserLoggedIn = true,
).execute(client) ).execute(client)
override fun getRemoteStatus( override fun getRemoteStatus(
path: String, path: String,
client: OwnCloudClient client: OwnCloudClient,
): RemoteOperationResult<RemoteServerInfo> = ): RemoteOperationResult<RemoteServerInfo> =
GetRemoteStatusOperation().execute(client) GetRemoteStatusOperation().execute(client)
} }

View File

@ -31,16 +31,16 @@ import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.http.methods.nonwebdav.HttpMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.HttpMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.webfinger.responses.WebfingerJrdResponse import com.owncloud.android.lib.resources.webfinger.responses.WebFingerResponse
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import timber.log.Timber import timber.log.Timber
import java.net.URL import java.net.URL
class GetOCInstanceViaWebfingerOperation( class GetInstancesViaWebFingerOperation(
private val lockupServerDomain: String, private val lockupServerDomain: String,
private val rel: String, private val rel: String,
private val resource: String, private val resource: String,
) : RemoteOperation<String>() { ) : RemoteOperation<List<String>>() {
private fun buildRequestUri() = private fun buildRequestUri() =
Uri.parse(lockupServerDomain).buildUpon() Uri.parse(lockupServerDomain).buildUpon()
@ -51,9 +51,9 @@ class GetOCInstanceViaWebfingerOperation(
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
private fun parseResponse(response: String): WebfingerJrdResponse { private fun parseResponse(response: String): WebFingerResponse {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(WebfingerJrdResponse::class.java) val adapter = moshi.adapter(WebFingerResponse::class.java)
return adapter.fromJson(response)!! return adapter.fromJson(response)!!
} }
@ -61,32 +61,31 @@ class GetOCInstanceViaWebfingerOperation(
method: HttpMethod, method: HttpMethod,
response: String?, response: String?,
status: Int status: Int
): RemoteOperationResult<String> { ): RemoteOperationResult<List<String>> {
Timber.e("Failed requesting webfinger info") Timber.e("Failed requesting WebFinger info")
if (response != null) { if (response != null) {
Timber.e("*** status code: $status; response message: $response") Timber.e("*** status code: $status; response message: $response")
} else { } else {
Timber.e("*** status code: $status") Timber.e("*** status code: $status")
} }
return RemoteOperationResult<String>(method) return RemoteOperationResult<List<String>>(method)
} }
private fun onRequestSuccessful(rawResponse: String): RemoteOperationResult<String> { private fun onRequestSuccessful(rawResponse: String): RemoteOperationResult<List<String>> {
val response = parseResponse(rawResponse) val response = parseResponse(rawResponse)
for (i in response.links) { Timber.d("Successful WebFinger request: $response")
if (i.rel == rel) { val operationResult = RemoteOperationResult<List<String>>(RemoteOperationResult.ResultCode.OK)
val operationResult = RemoteOperationResult<String>(RemoteOperationResult.ResultCode.OK) operationResult.data = response.links?.map { it.href } ?: listOf()
operationResult.data = i.href return operationResult
return operationResult
}
}
Timber.e("Could not find ownCloud relevant information in webfinger response: $rawResponse")
throw java.lang.Exception("Could not find ownCloud relevant information in webfinger response")
} }
override fun run(client: OwnCloudClient): RemoteOperationResult<String> { override fun run(client: OwnCloudClient): RemoteOperationResult<List<String>> {
val requestUri = buildRequestUri() val requestUri = buildRequestUri()
val getMethod = GetMethod(URL(requestUri.toString())) val getMethod = GetMethod(URL(requestUri.toString()))
// First iteration won't follow redirections.
getMethod.followRedirects = false
return try { return try {
val status = client.executeHttpMethod(getMethod) val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()!! val response = getMethod.getResponseBodyAsString()!!
@ -97,8 +96,8 @@ class GetOCInstanceViaWebfingerOperation(
onResultUnsuccessful(getMethod, response, status) onResultUnsuccessful(getMethod, response, status)
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Requesting webfinger info failed") Timber.e(e, "Requesting WebFinger info failed")
RemoteOperationResult<String>(e) RemoteOperationResult<List<String>>(e)
} }
} }

View File

@ -27,13 +27,13 @@ package com.owncloud.android.lib.resources.webfinger.responses
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class WebfingerJrdResponse( data class WebFingerResponse(
val subject: String, val subject: String,
val links: List<LinkItem> val links: List<LinkItem>?
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LinkItem( data class LinkItem(
val rel: String,
val href: String, val href: String,
val rel: String
) )

View File

@ -20,10 +20,11 @@ package com.owncloud.android.lib.resources.webfinger.services
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
interface WebfingerService { interface WebFingerService {
fun getInstanceFromWebfinger( fun getInstancesFromWebFinger(
lookupServer: String, lookupServer: String,
username: String, resource: String,
rel: String,
client: OwnCloudClient, client: OwnCloudClient,
): RemoteOperationResult<String> ): RemoteOperationResult<List<String>>
} }

View File

@ -19,20 +19,16 @@ package com.owncloud.android.lib.resources.webfinger.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.webfinger.GetOCInstanceViaWebfingerOperation import com.owncloud.android.lib.resources.webfinger.GetInstancesViaWebFingerOperation
import com.owncloud.android.lib.resources.webfinger.services.WebfingerService import com.owncloud.android.lib.resources.webfinger.services.WebFingerService
class OCWebfingerService : WebfingerService { class OCWebFingerService : WebFingerService {
override fun getInstanceFromWebfinger( override fun getInstancesFromWebFinger(
lookupServer: String, lookupServer: String,
username: String, resource: String,
rel: String,
client: OwnCloudClient, client: OwnCloudClient,
): RemoteOperationResult<String> = ): RemoteOperationResult<List<String>> =
GetOCInstanceViaWebfingerOperation(lookupServer, OWNCLOUD_REL, username).execute(client) GetInstancesViaWebFingerOperation(lookupServer, rel, resource).execute(client)
companion object {
private const val OWNCLOUD_REL = "http://webfinger.owncloud/rel/server-instance"
}
} }

View File

@ -0,0 +1,59 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 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
import android.os.Build
import com.owncloud.android.lib.resources.files.RemoteFile
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O], manifest = Config.NONE)
class RemoteFileTest {
@Test
fun getRemotePathFromUrl_legacyWebDav() {
val httpUrlToTest = "https://server.url/remote.php/dav/files/username/Documents/text.txt".toHttpUrl()
val expectedRemotePath = "/Documents/text.txt"
val actualRemotePath = RemoteFile.Companion.getRemotePathFromUrl(httpUrlToTest, "username")
assertEquals(expectedRemotePath, actualRemotePath)
}
@Test
fun getRemotePathFromUrl_spacesWebDav() {
val spaceWebDavUrl = "https://server.url/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9"
val httpUrlToTest =
"https://server.url/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9/Documents/text.txt".toHttpUrl()
val expectedRemotePath = "/Documents/text.txt"
val actualRemotePath = RemoteFile.Companion.getRemotePathFromUrl(httpUrlToTest, "username", spaceWebDavUrl)
assertEquals(expectedRemotePath, actualRemotePath)
}
}

View File

@ -8,34 +8,34 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
class WebfingerResponseTest { class WebFingerResponseTest {
lateinit var adapter: JsonAdapter<WebfingerJrdResponse> lateinit var adapter: JsonAdapter<WebFingerResponse>
private fun loadResponses(fileName: String) = adapter.fromJson(File(fileName).readText()) private fun loadResponses(fileName: String) = adapter.fromJson(File(fileName).readText())
@Before @Before
fun prepare() { fun prepare() {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
adapter = moshi.adapter(WebfingerJrdResponse::class.java) adapter = moshi.adapter(WebFingerResponse::class.java)
} }
@Test @Test
fun `check rel in too much information - ok`() { fun `check rel in too much information - ok`() {
val response = loadResponses(TOO_MUCH_INFORMATION_JSON)!! val response = loadResponses(TOO_MUCH_INFORMATION_JSON)!!
Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
Assert.assertEquals("http://webfinger.owncloud/rel/server-instance", response.links[0].rel) Assert.assertEquals("http://webfinger.owncloud/rel/server-instance", response.links!![0].rel)
} }
@Test(expected = JsonDataException::class) @Test(expected = JsonDataException::class)
fun `check key value pairs - ko - no href key`() { fun `check key value pairs - ko - no href key`() {
val response = loadResponses(BROKEN_JSON)!! val response = loadResponses(BROKEN_JSON)!!
Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
} }
@Test(expected = JsonDataException::class) @Test(expected = JsonDataException::class)
fun `check key value pairs - ko - no rel key`() { fun `check key value pairs - ko - no rel key`() {
val response = loadResponses(BROKEN_JSON)!! val response = loadResponses(BROKEN_JSON)!!
Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) Assert.assertEquals("https://gast.somedomain.de", response.links!![0].href)
} }
companion object { companion object {