diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt index 1e78bb6029..7960c2ed38 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperationIT.kt @@ -10,8 +10,8 @@ package com.owncloud.android.lib.resources.comments import com.owncloud.android.AbstractIT import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation -import com.owncloud.android.lib.resources.files.model.RemoteFile -import junit.framework.Assert.assertTrue +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test class CommentFileRemoteOperationIT : AbstractIT() { @@ -24,18 +24,20 @@ class CommentFileRemoteOperationIT : AbstractIT() { .execute(client).isSuccess ) - val readResult = ReadFileRemoteOperation(remotePath).execute(client) - val remoteFile = readResult.data.get(0) as RemoteFile + val readResult = ReadFileRemoteOperation(remotePath).execute(nextcloudClient) + val remoteFile = readResult.getResultData() + + assertNotNull(remoteFile) assertTrue( - CommentFileRemoteOperation("test", remoteFile.localId) + CommentFileRemoteOperation("test", remoteFile!!.localId) .execute(client) .isSuccess ) assertTrue( MarkCommentsAsReadRemoteOperation(remoteFile.localId) - .execute(client) + .execute(nextcloudClient) .isSuccess ) } diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/webdav/ChunkedFileUploadRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/webdav/ChunkedFileUploadRemoteOperationIT.kt index 31f834e3dc..48291fd79a 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/files/webdav/ChunkedFileUploadRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/files/webdav/ChunkedFileUploadRemoteOperationIT.kt @@ -7,6 +7,7 @@ */ package com.owncloud.android.lib.resources.files.webdav +import com.nextcloud.extensions.toLegacyPropset import com.owncloud.android.AbstractIT import com.owncloud.android.lib.common.network.WebdavEntry import com.owncloud.android.lib.common.network.WebdavUtils @@ -139,7 +140,7 @@ class ChunkedFileUploadRemoteOperationIT : AbstractIT() { private fun getRemoteSize(remotePath: String): Long { val davPath = client.filesDavUri.toString() + "/" + WebdavUtils.encodePath(remotePath) - val propFindMethod = PropFindMethod(davPath, WebdavUtils.getFilePropSet(), 0) + val propFindMethod = PropFindMethod(davPath, WebdavUtils.PROPERTYSETS.FILE.toLegacyPropset(), 0) client.executeMethod(propFindMethod) assert(propFindMethod.succeeded()) diff --git a/library/src/main/java/com/nextcloud/common/DavResponse.kt b/library/src/main/java/com/nextcloud/common/DavResponse.kt new file mode 100644 index 0000000000..1e33cabc09 --- /dev/null +++ b/library/src/main/java/com/nextcloud/common/DavResponse.kt @@ -0,0 +1,39 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.common + +import okhttp3.Headers +import okhttp3.internal.http.StatusLine + +/** + * Encapsulates essential data returned as responses from various DAV calls. + */ +data class DavResponse( + var success: Boolean = false, + var status: StatusLine? = null, + var headers: Headers? = null +) { + /** + * Return value of specified header. + * + * Simple helper to aid with nullability when called from Java. + * + * @param key name of header to get + * @return value of header or `null` when header is not set + */ + fun getHeader(key: String): String? = headers?.get(key) + + /** + * Return value of status code. + * + * Simple helper to aid with nullability when called from Java. + * + * @return HTTP status code or `0` if not set. + */ + fun getStatusCode(): Int = status?.code ?: 0 +} diff --git a/library/src/main/java/com/nextcloud/extensions/ArrayExtensions.kt b/library/src/main/java/com/nextcloud/extensions/ArrayExtensions.kt new file mode 100644 index 0000000000..16b3a0c0bb --- /dev/null +++ b/library/src/main/java/com/nextcloud/extensions/ArrayExtensions.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.extensions + +import at.bitfire.dav4jvm.Property +import org.apache.jackrabbit.webdav.property.DavPropertyName +import org.apache.jackrabbit.webdav.property.DavPropertyNameSet +import org.apache.jackrabbit.webdav.xml.Namespace + +/** + * Returns DavPropertyNameSet for given array of Property.Name. + * + * TODO: remove - only intended as a transitional aid + */ +fun Array.toLegacyPropset(): DavPropertyNameSet { + val propertySet = DavPropertyNameSet() + for (property in this) { + propertySet.add(DavPropertyName.create(property.name, Namespace.getNamespace(property.namespace))) + } + return propertySet +} \ No newline at end of file diff --git a/library/src/main/java/com/nextcloud/extensions/ParcelableExtensions.kt b/library/src/main/java/com/nextcloud/extensions/ParcelableExtensions.kt new file mode 100644 index 0000000000..7eb579925e --- /dev/null +++ b/library/src/main/java/com/nextcloud/extensions/ParcelableExtensions.kt @@ -0,0 +1,20 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.extensions + +import android.os.Build +import android.os.Parcel + +@Suppress("DEPRECATION") +inline fun Parcel.readParcelableArrayBridge(type: Class): Any? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.readParcelableArray(type::class.java.classLoader, this::class.java) + } else { + this.readParcelableArray(type::class.java.classLoader) + } +} \ No newline at end of file diff --git a/library/src/main/java/com/nextcloud/operations/MkColMethod.kt b/library/src/main/java/com/nextcloud/operations/MkColMethod.kt new file mode 100644 index 0000000000..6bf592779f --- /dev/null +++ b/library/src/main/java/com/nextcloud/operations/MkColMethod.kt @@ -0,0 +1,41 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.operations + +import android.net.Uri +import at.bitfire.dav4jvm.DavResource +import com.nextcloud.common.DavMethod +import com.nextcloud.common.DavResponse +import okhttp3.Headers.Companion.toHeaders +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Response +import okhttp3.internal.http.StatusLine + +class MkColMethod(httpUrl: HttpUrl) : DavMethod(httpUrl){ + private val headers = mutableMapOf() + + override fun apply(client: OkHttpClient, httpUrl: HttpUrl, filesDavUri: Uri): DavResponse { + val result = DavResponse() + + DavResource(client, httpUrl).mkCol( + xmlBody = null, + headers = headers.toHeaders() + ) { response: Response -> + result.success = response.isSuccessful + result.status = StatusLine.get(response) + result.headers = response.headers + } + + return result + } + + fun addRequestHeader(key: String, value: String) { + headers[key] = value + } +} \ No newline at end of file diff --git a/library/src/main/java/com/nextcloud/operations/MoveMethod.kt b/library/src/main/java/com/nextcloud/operations/MoveMethod.kt new file mode 100644 index 0000000000..d90e0d99e8 --- /dev/null +++ b/library/src/main/java/com/nextcloud/operations/MoveMethod.kt @@ -0,0 +1,43 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.operations + +import android.net.Uri +import at.bitfire.dav4jvm.DavResource +import com.nextcloud.common.DavMethod +import com.nextcloud.common.DavResponse +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.internal.http.StatusLine + +class MoveMethod( + httpUrl: HttpUrl, + private val destination: HttpUrl, + private val forceOverwrite: Boolean = false +): DavMethod(httpUrl) { + private val headers = mutableMapOf() + + override fun apply(client: OkHttpClient, httpUrl: HttpUrl, filesDavUri: Uri): DavResponse { + val result = DavResponse() + + DavResource(client, httpUrl).move( + destination = destination, + overwrite = forceOverwrite + ) { response -> + result.success = response.isSuccessful + result.status = StatusLine.get(response) + result.headers = response.headers + } + + return result + } + + fun addRequestHeader(key: String, value: String) { + headers[key] = value + } +} \ No newline at end of file diff --git a/library/src/main/java/com/nextcloud/operations/PropFindMethod.kt b/library/src/main/java/com/nextcloud/operations/PropFindMethod.kt index 5333db2a1e..4f527c6427 100644 --- a/library/src/main/java/com/nextcloud/operations/PropFindMethod.kt +++ b/library/src/main/java/com/nextcloud/operations/PropFindMethod.kt @@ -45,21 +45,24 @@ class PropFindMethod ) : DavMethod(httpUrl) { override fun apply(client: OkHttpClient, httpUrl: HttpUrl, filesDavUri: Uri): PropFindResult { - val webDavFileUtils = WebDavFileUtils() val result = PropFindResult() DavResource(client, httpUrl).propfind( depth, *propertySet ) { response: Response, hrefRelation: Response.HrefRelation? -> - result.success = response.isSuccess() + result.davResponse.success = response.isSuccess() + response.status?.let { status -> + result.davResponse.status = status + } + when (hrefRelation) { Response.HrefRelation.MEMBER -> result.children.add( - webDavFileUtils.parseResponse(response, filesDavUri) + WebDavFileUtils.parseResponse(response, filesDavUri) ) Response.HrefRelation.SELF -> result.root = - webDavFileUtils.parseResponse(response, filesDavUri) + WebDavFileUtils.parseResponse(response, filesDavUri) Response.HrefRelation.OTHER -> {} else -> {} diff --git a/library/src/main/java/com/nextcloud/operations/PropFindResult.kt b/library/src/main/java/com/nextcloud/operations/PropFindResult.kt index c9f5ff22ff..026a176308 100644 --- a/library/src/main/java/com/nextcloud/operations/PropFindResult.kt +++ b/library/src/main/java/com/nextcloud/operations/PropFindResult.kt @@ -22,11 +22,15 @@ package com.nextcloud.operations +import com.nextcloud.common.DavResponse import com.owncloud.android.lib.resources.files.model.RemoteFile data class PropFindResult( - var success: Boolean = false, + val davResponse: DavResponse = DavResponse(), var root: RemoteFile = RemoteFile(), val children: MutableList = mutableListOf() -) - +) { + fun getContent(): List { + return children + root + } +} \ No newline at end of file diff --git a/library/src/main/java/com/nextcloud/operations/PropPatchMethod.kt b/library/src/main/java/com/nextcloud/operations/PropPatchMethod.kt new file mode 100644 index 0000000000..1969caee6f --- /dev/null +++ b/library/src/main/java/com/nextcloud/operations/PropPatchMethod.kt @@ -0,0 +1,36 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.operations + +import android.net.Uri +import at.bitfire.dav4jvm.DavResource +import at.bitfire.dav4jvm.Property +import at.bitfire.dav4jvm.Response +import com.nextcloud.common.DavMethod +import com.nextcloud.common.DavResponse +import okhttp3.HttpUrl +import okhttp3.OkHttpClient + +class PropPatchMethod +@JvmOverloads constructor( + httpUrl: HttpUrl, + private val setProperties: Map = emptyMap(), + private val removeProperties: List = emptyList() +) : DavMethod(httpUrl){ + override fun apply(client: OkHttpClient, httpUrl: HttpUrl, filesDavUri: Uri): DavResponse { + val result = DavResponse() + DavResource(client, httpUrl).proppatch(setProperties, removeProperties) { response: Response, hrefRelation: Response.HrefRelation? -> + result.success = response.isSuccess() + response.status?.let { status -> + result.status = status + } + } + + return result + } +} \ No newline at end of file diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt b/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt index 90e1c4d47c..ab36edf002 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt +++ b/library/src/main/java/com/owncloud/android/lib/common/network/ExtendedProperties.kt @@ -1,36 +1,17 @@ -/* Nextcloud Android Library is available under MIT license -* -* @author ZetaZom -* Copyright (C) 2024 ZetaTom -* Copyright (C) 2024 Nextcloud 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. -* -*/ - +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-License-Identifier: MIT + */ package com.owncloud.android.lib.common.network import at.bitfire.dav4jvm.Property enum class ExtendedProperties(val value: String, val namespace: String) { CREATION_TIME("creation_time", WebdavUtils.NAMESPACE_NC), + COMMENTS_READ_MARKER("readMarker", WebdavUtils.NAMESPACE_NC), FAVORITE("favorite", WebdavUtils.NAMESPACE_OC), HAS_PREVIEW("has-preview", WebdavUtils.NAMESPACE_NC), HIDDEN("hidden", WebdavUtils.NAMESPACE_NC), diff --git a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.kt b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.kt index 67d250e392..0be36f835b 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.kt +++ b/library/src/main/java/com/owncloud/android/lib/common/network/WebdavUtils.kt @@ -29,11 +29,11 @@ package com.owncloud.android.lib.common.network import android.net.Uri import at.bitfire.dav4jvm.PropertyRegistry.register -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.ResourceType +import at.bitfire.dav4jvm.property.webdav.CreationDate +import at.bitfire.dav4jvm.property.webdav.DisplayName +import at.bitfire.dav4jvm.property.webdav.GetContentLength +import at.bitfire.dav4jvm.property.webdav.GetContentType +import at.bitfire.dav4jvm.property.webdav.ResourceType import com.google.gson.Gson import com.owncloud.android.lib.resources.files.webdav.NCCreationTime import com.owncloud.android.lib.resources.files.webdav.NCEncrypted @@ -205,7 +205,7 @@ object WebdavUtils { try { date?.let { return format.parse(it) } } catch (e: ParseException) { - // this is not the format + // wrong format } } return null @@ -254,19 +254,42 @@ object WebdavUtils { fun registerCustomFactories() { val list = listOf( + NCCreationTime.Factory(), + NCEncrypted.Factory(), + NCEtag.Factory(), NCFavorite.Factory(), NCGetLastModified.Factory(), - NCEtag.Factory(), - NCPermissions.Factory(), - OCId.Factory(), - OCSize.Factory(), + NCHidden.Factory(), + NCLock.Factory(), + NCLockOwnerDisplayName.Factory(), + NCLockOwnerEditor.Factory(), + NCLockOwner.Factory(), + NCLockOwnerType.Factory(), + NCLockTime.Factory(), + NCLockTimeout.Factory(), + NCLockToken.Factory(), + NCMetadataGPS.Factory(), + NCMetadataLivePhoto.Factory(), + NCMetadataPhotosGPS.Factory(), + NCMetadataPhotosSize.Factory(), + NCMetadataSize.Factory(), NCMountType.Factory(), - OCOwnerId.Factory(), - OCOwnerDisplayName.Factory(), + NCNote.Factory(), + NCPermissions.Factory(), + NCPreview.Factory(), NCRichWorkspace.Factory(), NCSharees.Factory(), NCTags.Factory(), - OCLocalId.Factory() + NCTrashbinDeletionTime.Factory(), + NCTrashbinFilename.Factory(), + NCTrashbinLocation.Factory(), + NCUploadTime.Factory(), + OCCommentsUnread.Factory(), + OCId.Factory(), + OCLocalId.Factory(), + OCOwnerDisplayName.Factory(), + OCOwnerId.Factory(), + OCSize.Factory(), ) register(list) } diff --git a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java deleted file mode 100644 index 41b6701a49..0000000000 --- a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Nextcloud Android Library - * - * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2022 Álvaro Brey - * SPDX-FileCopyrightText: 2019-2021 Tobias Kaminsky - * SPDX-FileCopyrightText: 2017 Andy Scherzinger - * SPDX-FileCopyrightText: 2014-2016 ownCloud Inc. - * SPDX-FileCopyrightText: 2015 masensio - * SPDX-FileCopyrightText: 2014 David A. Velasco - * SPDX-FileCopyrightText: 2014 Jorge Antonio Diaz-Benito Soriano - * SPDX-FileCopyrightText: 2014-2016 Juan Carlos González Cabrero - * SPDX-FileCopyrightText: 2014 jabarros - * SPDX-License-Identifier: MIT - */ -package com.owncloud.android.lib.common.operations; - -import android.accounts.Account; -import android.accounts.AccountsException; -import android.os.Build; -import android.system.ErrnoException; -import android.system.OsConstants; - -import com.nextcloud.common.OkHttpMethodBase; -import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; -import com.owncloud.android.lib.common.network.CertificateCombinedException; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.files.CreateLocalFileException; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavException; -import org.json.JSONException; - -import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.ConnectException; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Locale; - -import javax.net.ssl.SSLException; - -import okhttp3.Headers; - - -/** - * The result of a remote operation required to an ownCloud server. - *

- * Provides a common classification of remote operation results for all the application. - * - * @author David A. Velasco - */ -public class RemoteOperationResult implements Serializable { - - // Generated - should be refreshed every time the class changes!! - private static final long serialVersionUID = -4325446958558896222L; - private static final String TAG = RemoteOperationResult.class.getSimpleName(); - private static final String HEADER_WWW_AUTHENTICATE = "www-authenticate"; - private static final String HEADER_LOCATION = "location"; - - public enum ResultCode { - OK, - OK_SSL, - OK_NO_SSL, - UNHANDLED_HTTP_CODE, - UNAUTHORIZED, - FILE_NOT_FOUND, - INSTANCE_NOT_CONFIGURED, - UNKNOWN_ERROR, - WRONG_CONNECTION, - TIMEOUT, - INCORRECT_ADDRESS, - HOST_NOT_AVAILABLE, - NO_NETWORK_CONNECTION, - SSL_ERROR, - SSL_RECOVERABLE_PEER_UNVERIFIED, - BAD_OC_VERSION, - CANCELLED, - INVALID_LOCAL_FILE_NAME, - INVALID_OVERWRITE, - CONFLICT, - OAUTH2_ERROR, - SYNC_CONFLICT, - LOCAL_STORAGE_FULL, - LOCAL_STORAGE_NOT_MOVED, - LOCAL_STORAGE_NOT_COPIED, - OAUTH2_ERROR_ACCESS_DENIED, - QUOTA_EXCEEDED, - ACCOUNT_NOT_FOUND, - ACCOUNT_EXCEPTION, - ACCOUNT_NOT_NEW, - ACCOUNT_NOT_THE_SAME, - INVALID_CHARACTER_IN_NAME, - SHARE_NOT_FOUND, - LOCAL_STORAGE_NOT_REMOVED, - FORBIDDEN, - SHARE_FORBIDDEN, - OK_REDIRECT_TO_NON_SECURE_CONNECTION, - INVALID_MOVE_INTO_DESCENDANT, - INVALID_COPY_INTO_DESCENDANT, - PARTIAL_MOVE_DONE, - PARTIAL_COPY_DONE, - SHARE_WRONG_PARAMETER, - WRONG_SERVER_RESPONSE, - INVALID_CHARACTER_DETECT_IN_SERVER, - DELAYED_FOR_WIFI, - DELAYED_FOR_CHARGING, - LOCAL_FILE_NOT_FOUND, - NOT_AVAILABLE, - MAINTENANCE_MODE, - LOCK_FAILED, - DELAYED_IN_POWER_SAVE_MODE, - ACCOUNT_USES_STANDARD_PASSWORD, - METADATA_NOT_FOUND, - OLD_ANDROID_API, - UNTRUSTED_DOMAIN, - ETAG_CHANGED, - ETAG_UNCHANGED, - VIRUS_DETECTED, - FOLDER_ALREADY_EXISTS, - CANNOT_CREATE_FILE - } - - private boolean mSuccess = false; - private int mHttpCode = -1; - private String mHttpPhrase = null; - private Exception mException = null; - private ResultCode mCode = ResultCode.UNKNOWN_ERROR; - private String message; - private String mRedirectedLocation; - private ArrayList mAuthenticateHeaders = new ArrayList<>(); - private String mLastPermanentLocation = null; - - private ArrayList mData; - private T resultData; - - /** - * Public constructor from result code. - *

- * To be used when the caller takes the responsibility of interpreting the result of a {@link RemoteOperation} - * - * @param code {@link ResultCode} decided by the caller. - */ - public RemoteOperationResult(ResultCode code) { - mCode = code; - mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL || - code == ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION || code == ResultCode.ETAG_CHANGED || - code == ResultCode.ETAG_UNCHANGED); - mData = null; - } - - private RemoteOperationResult(boolean success, int httpCode) { - mSuccess = success; - mHttpCode = httpCode; - - if (success) { - mCode = ResultCode.OK; - - } else if (httpCode > 0) { - switch (httpCode) { - case HttpStatus.SC_UNAUTHORIZED: - mCode = ResultCode.UNAUTHORIZED; - break; - case HttpStatus.SC_NOT_FOUND: - mCode = ResultCode.FILE_NOT_FOUND; - break; - case HttpStatus.SC_INTERNAL_SERVER_ERROR: - mCode = ResultCode.INSTANCE_NOT_CONFIGURED; - break; - case HttpStatus.SC_CONFLICT: - mCode = ResultCode.CONFLICT; - break; - case HttpStatus.SC_INSUFFICIENT_STORAGE: - mCode = ResultCode.QUOTA_EXCEEDED; - break; - case HttpStatus.SC_FORBIDDEN: - mCode = ResultCode.FORBIDDEN; - break; - case HttpStatus.SC_SERVICE_UNAVAILABLE: - mCode = ResultCode.MAINTENANCE_MODE; - break; - default: - mCode = ResultCode.UNHANDLED_HTTP_CODE; - Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode); - } - } - } - - public RemoteOperationResult(boolean success, int httpCode, Header[] headers) { - this(success, httpCode); - - if (headers != null) { - for (Header header : headers) { - if (HEADER_LOCATION.equals(header.getName().toLowerCase(Locale.US))) { - mRedirectedLocation = header.getValue(); - - } else if (HEADER_WWW_AUTHENTICATE.equals(header.getName().toLowerCase(Locale.US))) { - mAuthenticateHeaders.add(header.getValue()); - } - } - } - if (isIdPRedirection()) { - mCode = ResultCode.UNAUTHORIZED; // overrides default ResultCode.UNKNOWN - } - } - - public RemoteOperationResult(boolean success, String bodyResponse, int httpCode) { - mSuccess = success; - mHttpCode = httpCode; - - if (success) { - mCode = ResultCode.OK; - } else if (httpCode > 0) { - switch (httpCode) { - case HttpStatus.SC_BAD_REQUEST: - try { - InputStream is = new ByteArrayInputStream(bodyResponse.getBytes()); - ExceptionParser xmlParser = new ExceptionParser(is); - if (xmlParser.isInvalidCharacterException()) { - mCode = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER; - } - } catch (Exception e) { - mCode = ResultCode.UNHANDLED_HTTP_CODE; - Log_OC.e(TAG, "Exception reading exception from server", e); - } - break; - default: - mCode = ResultCode.UNHANDLED_HTTP_CODE; - Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode); - } - } - } - - /** - * Public constructor from exception. - * - * To be used when an exception prevented the end of the {@link RemoteOperation}. - * - * Determines a {@link ResultCode} depending on the type of the exception. - * - * @param e Exception that interrupted the {@link RemoteOperation} - */ - public RemoteOperationResult(Exception e) { - mException = e; - - if (e instanceof OperationCancelledException) { - mCode = ResultCode.CANCELLED; - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && e instanceof ErrnoException && ((ErrnoException) e).errno == OsConstants.ENOTCONN) { - mCode = ResultCode.NO_NETWORK_CONNECTION; - } else if (e instanceof ConnectException) { - mCode = ResultCode.HOST_NOT_AVAILABLE; - } else if (e instanceof SocketException) { - mCode = ResultCode.WRONG_CONNECTION; - } else if (e instanceof SocketTimeoutException) { - mCode = ResultCode.TIMEOUT; - } else if (e instanceof ConnectTimeoutException) { - mCode = ResultCode.TIMEOUT; - } else if (e instanceof MalformedURLException) { - mCode = ResultCode.INCORRECT_ADDRESS; - } else if (e instanceof UnknownHostException) { - mCode = ResultCode.HOST_NOT_AVAILABLE; - } else if (e instanceof AccountNotFoundException) { - mCode = ResultCode.ACCOUNT_NOT_FOUND; - } else if (e instanceof AccountsException) { - mCode = ResultCode.ACCOUNT_EXCEPTION; - } else if (e instanceof SSLException || e instanceof RuntimeException) { - CertificateCombinedException se = getCertificateCombinedException(e); - if (se != null) { - mException = se; - if (se.isRecoverable()) { - mCode = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED; - } - } else if (e instanceof RuntimeException) { - mCode = ResultCode.HOST_NOT_AVAILABLE; - - } else { - mCode = ResultCode.SSL_ERROR; - } - } else if (e instanceof FileNotFoundException) { - mCode = ResultCode.LOCAL_FILE_NOT_FOUND; - } else if (e instanceof CreateLocalFileException) { - if (((CreateLocalFileException) e).isCausedByInvalidPath()) { - mCode = ResultCode.INVALID_LOCAL_FILE_NAME; - } else { - mCode = ResultCode.CANNOT_CREATE_FILE; - } - } else { - mCode = ResultCode.UNKNOWN_ERROR; - } - } - - public RemoteOperationResult(boolean success, OkHttpMethodBase httpMethod) { - this(success, httpMethod.getStatusCode(), httpMethod.getStatusText(), httpMethod.getResponseHeaders()); - } - - /** - * Public constructor from separate elements of an HTTP or DAV response. - * - * To be used when the result needs to be interpreted from the response of an HTTP/DAV method. - * - * Determines a {@link ResultCode} from the already executed method received as a parameter. Generally, - * will depend on the HTTP code and HTTP response headers received. In some cases will inspect also the - * response body. - * - * @param success The operation was considered successful or not. - * @param httpMethod HTTP/DAV method already executed which response will be examined to interpret the - * result. - */ - public RemoteOperationResult(boolean success, HttpMethod httpMethod) { - this(success, httpMethod.getStatusCode(), httpMethod.getStatusText(), httpMethod.getResponseHeaders()); - - if (mHttpCode == HttpStatus.SC_BAD_REQUEST || mHttpCode == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE) { - try { - String bodyResponse = httpMethod.getResponseBodyAsString(); - - if (bodyResponse != null && bodyResponse.length() > 0) { - InputStream is = new ByteArrayInputStream(bodyResponse.getBytes()); - ExceptionParser xmlParser = new ExceptionParser(is); - - if (xmlParser.isInvalidCharacterException()) { - mCode = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER; - } - if (xmlParser.isVirusException()) { - mCode = ResultCode.VIRUS_DETECTED; - } - - mHttpPhrase = xmlParser.getMessage(); - } - } catch (Exception e) { - Log_OC.w(TAG, "Error reading exception from server: " + e.getMessage()); - // mCode stays as set in this(success, httpCode, headers) - } - } - } - - /** - * Public constructor from separate elements of an HTTP or DAV response. - * - * To be used when the result needs to be interpreted from HTTP response elements that could come from - * different requests (WARNING: black magic, try to avoid). - * - * If all the fields come from the same HTTP/DAV response, {@link #RemoteOperationResult(boolean, HttpMethod)} - * should be used instead. - * - * Determines a {@link ResultCode} depending on the HTTP code and HTTP response headers received. - * - * @param success The operation was considered successful or not. - * @param httpCode HTTP status code returned by an HTTP/DAV method. - * @param httpPhrase HTTP status line phrase returned by an HTTP/DAV method - * @param httpHeaders HTTP response header returned by an HTTP/DAV method - */ - public RemoteOperationResult(boolean success, int httpCode, String httpPhrase, Header[] httpHeaders) { - this(success, httpCode, httpPhrase); - if (httpHeaders != null) { - Header current; - for (Header httpHeader : httpHeaders) { - current = httpHeader; - if (HEADER_WWW_AUTHENTICATE.equals(current.getName().toLowerCase(Locale.US))) { - mAuthenticateHeaders.add(current.getValue()); - } else if (HEADER_LOCATION.equals(current.getName().toLowerCase(Locale.US)) && mAuthenticateHeaders.isEmpty()) { - mRedirectedLocation = current.getValue(); - } - } - } - if (isIdPRedirection()) { - mCode = ResultCode.UNAUTHORIZED; // overrides default ResultCode.UNKNOWN - } - } - - /** - * Public constructor from separate elements of an HTTP or DAV response. - * - * To be used when the result needs to be interpreted from HTTP response elements that could come from - * different requests (WARNING: black magic, try to avoid). - * - * If all the fields come from the same HTTP/DAV response, {@link #RemoteOperationResult(boolean, HttpMethod)} - * should be used instead. - * - * Determines a {@link ResultCode} depending on the HTTP code and HTTP response headers received. - * - * @param success The operation was considered successful or not. - * @param httpCode HTTP status code returned by an HTTP/DAV method. - * @param httpPhrase HTTP status line phrase returned by an HTTP/DAV method - * @param httpHeaders HTTP response header returned by an HTTP/DAV method - */ - public RemoteOperationResult(boolean success, - int httpCode, - String httpPhrase, - Headers httpHeaders) { - this(success, httpCode, httpPhrase); - - String location = httpHeaders.get(HEADER_LOCATION); - if (location != null) { - mRedirectedLocation = location; - } - - String auth = httpHeaders.get(HEADER_WWW_AUTHENTICATE); - if (auth != null) { - mAuthenticateHeaders.add(auth); - } - - if (isIdPRedirection()) { - mCode = ResultCode.UNAUTHORIZED; // overrides default ResultCode.UNKNOWN - } - } - - /** - * Private constructor for results built interpreting a HTTP or DAV response. - * - * Determines a {@link ResultCode} depending of the type of the exception. - * - * @param success Operation was successful or not. - * @param httpCode HTTP status code returned by the HTTP/DAV method. - * @param httpPhrase HTTP status line phrase returned by the HTTP/DAV method - */ - private RemoteOperationResult(boolean success, int httpCode, String httpPhrase) { - mSuccess = success; - mHttpCode = httpCode; - mHttpPhrase = httpPhrase; - - if (success) { - mCode = ResultCode.OK; - - } else if (httpCode > 0) { - switch (httpCode) { - case HttpStatus.SC_UNAUTHORIZED: // 401 - mCode = ResultCode.UNAUTHORIZED; - break; - case HttpStatus.SC_FORBIDDEN: // 403 - mCode = ResultCode.FORBIDDEN; - break; - case HttpStatus.SC_NOT_FOUND: // 404 - mCode = ResultCode.FILE_NOT_FOUND; - break; - case HttpStatus.SC_CONFLICT: // 409 - mCode = ResultCode.CONFLICT; - break; - case HttpStatus.SC_INTERNAL_SERVER_ERROR: // 500 - mCode = ResultCode.INSTANCE_NOT_CONFIGURED; // assuming too much... - break; - case HttpStatus.SC_SERVICE_UNAVAILABLE: // 503 - mCode = ResultCode.MAINTENANCE_MODE; - break; - case HttpStatus.SC_INSUFFICIENT_STORAGE: // 507 - mCode = ResultCode.QUOTA_EXCEEDED; - break; - default: - mCode = ResultCode.UNHANDLED_HTTP_CODE; // UNKNOWN ERROR - Log_OC.d(TAG, - "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " - + mHttpCode + " " + mHttpPhrase); - } - } - } - - /** - * @deprecated use setResultData() instead - */ - @Deprecated - public void setData(ArrayList files) { - mData = files; - } - - /** - * @deprecated use setResultData() instead - */ - @Deprecated - public void setSingleData(Object object) { - mData = new ArrayList<>(Collections.singletonList(object)); - } - - public void setResultData(T object) { - resultData = object; - } - - public T getResultData() { - if (!mSuccess) { - throw new RuntimeException("Accessing result data after operation failed!"); - } - return resultData; - } - - /** - * @deprecated use getResultData() instead - */ - @Deprecated - public ArrayList getData() { - if (!mSuccess) { - throw new RuntimeException("Accessing result data after operation failed!"); - } - if (mData != null) { - return mData; - } else if (resultData instanceof ArrayList) { - return (ArrayList) resultData; - } else { - return null; - } - } - - /** - * @deprecated use getResultData() instead - */ - @Deprecated - public Object getSingleData() { - if (!mSuccess) { - throw new RuntimeException("Accessing result data after operation failed!"); - } - return mData.get(0); - } - - public boolean isSuccess() { - return mSuccess; - } - - public boolean isCancelled() { - return mCode == ResultCode.CANCELLED; - } - - public int getHttpCode() { - return mHttpCode; - } - - public String getHttpPhrase() { - return mHttpPhrase; - } - - public ResultCode getCode() { - return mCode; - } - - public Exception getException() { - return mException; - } - - public boolean isSslRecoverableException() { - return mCode == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED; - } - - public boolean isRedirectToNonSecureConnection() { - return mCode == ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION; - } - - private CertificateCombinedException getCertificateCombinedException(Exception e) { - CertificateCombinedException result = null; - if (e instanceof CertificateCombinedException) { - return (CertificateCombinedException) e; - } - Throwable cause = mException.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && - !(cause instanceof CertificateCombinedException)) { - previousCause = cause; - cause = cause.getCause(); - } - if (cause instanceof CertificateCombinedException) { - result = (CertificateCombinedException) cause; - } - return result; - } - - public String getLogMessage() { - - if (mException != null) { - if (mException instanceof OperationCancelledException) { - return "Operation cancelled by the caller"; - - } else if (mException instanceof SocketException) { - return "Socket exception"; - - } else if (mException instanceof SocketTimeoutException) { - return "Socket timeout exception"; - - } else if (mException instanceof ConnectTimeoutException) { - return "Connect timeout exception"; - - } else if (mException instanceof MalformedURLException) { - return "Malformed URL exception"; - - } else if (mException instanceof UnknownHostException) { - return "Unknown host exception"; - - } else if (mException instanceof CertificateCombinedException) { - if (((CertificateCombinedException) mException).isRecoverable()) - return "SSL recoverable exception"; - else - return "SSL exception"; - - } else if (mException instanceof SSLException) { - return "SSL exception"; - - } else if (mException instanceof DavException) { - return "Unexpected WebDAV exception"; - - } else if (mException instanceof HttpException) { - return "HTTP violation"; - - } else if (mException instanceof IOException) { - return "Unrecovered transport exception"; - - } else if (mException instanceof AccountNotFoundException) { - Account failedAccount = - ((AccountNotFoundException)mException).getFailedAccount(); - return mException.getMessage() + " (" + - (failedAccount != null ? failedAccount.name : "NULL") + ")"; - - } else if (mException instanceof AccountsException) { - return "Exception while using account"; - - } else if (mException instanceof JSONException) { - return "JSON exception"; - - } else { - return "Unexpected exception"; - } - } - - if (mCode == ResultCode.INSTANCE_NOT_CONFIGURED) { - return "The Nextcloud server is not configured!"; - - } else if (mCode == ResultCode.NO_NETWORK_CONNECTION) { - return "No network connection"; - - } else if (mCode == ResultCode.BAD_OC_VERSION) { - return "No valid Nextcloud version was found at the server"; - - } else if (mCode == ResultCode.LOCAL_STORAGE_FULL) { - return "Local storage full"; - - } else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) { - return "Error while moving file to final directory"; - - } else if (mCode == ResultCode.ACCOUNT_NOT_NEW) { - return "Account already existing when creating a new one"; - - } else if (mCode == ResultCode.ACCOUNT_NOT_THE_SAME) { - return "Authenticated with a different account than the one updating"; - - } else if (mCode == ResultCode.INVALID_CHARACTER_IN_NAME) { - return "The file name contains an forbidden character"; - - } else if (mCode == ResultCode.FILE_NOT_FOUND) { - return "Local file does not exist"; - - } else if (mCode == ResultCode.SYNC_CONFLICT) { - return "Synchronization conflict"; - } - - return "Operation finished with HTTP status code " + mHttpCode + " (" + - (isSuccess() ? "success" : "fail") + ")"; - - } - - public boolean isServerFail() { - return (mHttpCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR); - } - - public boolean isException() { - return (mException != null); - } - - public boolean isTemporalRedirection() { - return (mHttpCode == 302 || mHttpCode == 307); - } - - public String getRedirectedLocation() { - return mRedirectedLocation; - } - - public final boolean isIdPRedirection() { - return (mRedirectedLocation != null && - (mRedirectedLocation.toUpperCase(Locale.US).contains("SAML") || - mRedirectedLocation.toLowerCase(Locale.US).contains("wayf"))); - } - - /** - * Checks if is a non https connection - * - * @return boolean true/false - */ - public boolean isNonSecureRedirection() { - return (mRedirectedLocation != null && !(mRedirectedLocation.toLowerCase(Locale.US).startsWith("https://"))); - } - - public ArrayList getAuthenticateHeaders() { - return mAuthenticateHeaders; - } - - public String getLastPermanentLocation() { - return mLastPermanentLocation; - } - - public void setLastPermanentLocation(String lastPermanentLocation) { - mLastPermanentLocation = lastPermanentLocation; - } - - public void setMessage(String message) { - this.message = message; - } - - /** - * Message that is returned by server, e.g. password policy violation on ocs share api - * @return message that can be shown to user - */ - public String getMessage() { - return message; - } - - @Override - public String toString() { - return "RemoteOperationResult{" + - "mSuccess=" + mSuccess + - ", mHttpCode=" + mHttpCode + - ", mHttpPhrase='" + mHttpPhrase + '\'' + - ", mException=" + mException + - ", mCode=" + mCode + - ", message='" + message + '\'' + - ", getLogMessage='" + getLogMessage() + '\'' + - '}'; - } -} diff --git a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.kt b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.kt new file mode 100644 index 0000000000..81401b0046 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.kt @@ -0,0 +1,597 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2022 Álvaro Brey + * SPDX-FileCopyrightText: 2019-2021 Tobias Kaminsky + * SPDX-FileCopyrightText: 2017 Andy Scherzinger + * SPDX-FileCopyrightText: 2014-2016 ownCloud Inc. + * SPDX-FileCopyrightText: 2015 masensio + * SPDX-FileCopyrightText: 2014 David A. Velasco + * SPDX-FileCopyrightText: 2014 Jorge Antonio Diaz-Benito Soriano + * SPDX-FileCopyrightText: 2014-2016 Juan Carlos González Cabrero + * SPDX-FileCopyrightText: 2014 jabarros + * SPDX-License-Identifier: MIT + */ + +package com.owncloud.android.lib.common.operations + +import android.accounts.AccountsException +import android.os.Build +import android.system.ErrnoException +import android.system.OsConstants +import com.nextcloud.common.DavResponse +import com.nextcloud.common.OkHttpMethodBase +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.network.CertificateCombinedException +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.files.CreateLocalFileException +import okhttp3.Headers +import org.apache.commons.httpclient.ConnectTimeoutException +import org.apache.commons.httpclient.Header +import org.apache.commons.httpclient.HttpException +import org.apache.commons.httpclient.HttpMethod +import org.apache.commons.httpclient.HttpStatus +import org.apache.jackrabbit.webdav.DavException +import org.json.JSONException +import java.io.ByteArrayInputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream +import java.io.Serial +import java.io.Serializable +import java.net.ConnectException +import java.net.MalformedURLException +import java.net.SocketException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLException + +/** + * The result of a remote operation required to an ownCloud server. + * + * + * Provides a common classification of remote operation results for all the application. + * + * @author David A. Velasco + */ +class RemoteOperationResult : Serializable { + enum class ResultCode { + OK, + OK_SSL, + OK_NO_SSL, + UNHANDLED_HTTP_CODE, + UNAUTHORIZED, + FILE_NOT_FOUND, + INSTANCE_NOT_CONFIGURED, + UNKNOWN_ERROR, + WRONG_CONNECTION, + TIMEOUT, + INCORRECT_ADDRESS, + HOST_NOT_AVAILABLE, + NO_NETWORK_CONNECTION, + SSL_ERROR, + SSL_RECOVERABLE_PEER_UNVERIFIED, + BAD_OC_VERSION, + CANCELLED, + INVALID_LOCAL_FILE_NAME, + INVALID_OVERWRITE, + CONFLICT, + OAUTH2_ERROR, + SYNC_CONFLICT, + LOCAL_STORAGE_FULL, + LOCAL_STORAGE_NOT_MOVED, + LOCAL_STORAGE_NOT_COPIED, + OAUTH2_ERROR_ACCESS_DENIED, + QUOTA_EXCEEDED, + ACCOUNT_NOT_FOUND, + ACCOUNT_EXCEPTION, + ACCOUNT_NOT_NEW, + ACCOUNT_NOT_THE_SAME, + INVALID_CHARACTER_IN_NAME, + SHARE_NOT_FOUND, + LOCAL_STORAGE_NOT_REMOVED, + FORBIDDEN, + SHARE_FORBIDDEN, + OK_REDIRECT_TO_NON_SECURE_CONNECTION, + INVALID_MOVE_INTO_DESCENDANT, + INVALID_COPY_INTO_DESCENDANT, + PARTIAL_MOVE_DONE, + PARTIAL_COPY_DONE, + SHARE_WRONG_PARAMETER, + WRONG_SERVER_RESPONSE, + INVALID_CHARACTER_DETECT_IN_SERVER, + DELAYED_FOR_WIFI, + DELAYED_FOR_CHARGING, + LOCAL_FILE_NOT_FOUND, + NOT_AVAILABLE, + MAINTENANCE_MODE, + LOCK_FAILED, + DELAYED_IN_POWER_SAVE_MODE, + ACCOUNT_USES_STANDARD_PASSWORD, + METADATA_NOT_FOUND, + OLD_ANDROID_API, + UNTRUSTED_DOMAIN, + ETAG_CHANGED, + ETAG_UNCHANGED, + VIRUS_DETECTED, + FOLDER_ALREADY_EXISTS, + CANNOT_CREATE_FILE + } + + var isSuccess = false + private set + var httpCode = -1 + private set + var httpPhrase: String? = null + private set + var exception: Exception? = null + private set + var code = ResultCode.UNKNOWN_ERROR + private set + + /** + * Message that is returned by server, e.g. password policy violation on ocs share api + * @return message that can be shown to user + */ + @JvmField + var message: String? = null + var redirectedLocation: String? = null + private set + val authenticateHeaders = ArrayList() + private var mData: ArrayList? = null + private var resultData: T? = null + + /** + * Public constructor from result code. + * + * + * To be used when the caller takes the responsibility of interpreting the result of a [RemoteOperation] + * + * @param code [ResultCode] decided by the caller. + */ + constructor(code: ResultCode) { + this.code = code + isSuccess = code in listOf( + ResultCode.OK, + ResultCode.OK_SSL, + ResultCode.OK_NO_SSL, + ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION, + ResultCode.ETAG_CHANGED, + ResultCode.ETAG_UNCHANGED + ) + mData = null + } + + private constructor(success: Boolean, httpCode: Int) { + isSuccess = success + this.httpCode = httpCode + if (success) { + this.code = ResultCode.OK + } else if (httpCode > 0) { + this.code = when (httpCode) { + HttpStatus.SC_UNAUTHORIZED -> ResultCode.UNAUTHORIZED + HttpStatus.SC_NOT_FOUND -> ResultCode.FILE_NOT_FOUND + HttpStatus.SC_INTERNAL_SERVER_ERROR -> ResultCode.INSTANCE_NOT_CONFIGURED + HttpStatus.SC_CONFLICT -> ResultCode.CONFLICT + HttpStatus.SC_INSUFFICIENT_STORAGE -> ResultCode.QUOTA_EXCEEDED + HttpStatus.SC_FORBIDDEN -> ResultCode.FORBIDDEN + HttpStatus.SC_SERVICE_UNAVAILABLE -> ResultCode.MAINTENANCE_MODE + else -> { + Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: $httpCode") + ResultCode.UNHANDLED_HTTP_CODE + } + } + } + } + + constructor(success: Boolean, bodyResponse: String, httpCode: Int) { + isSuccess = success + this.httpCode = httpCode + if (success) { + this.code = ResultCode.OK + } else if (httpCode > 0) { + if (httpCode == HttpStatus.SC_BAD_REQUEST) { + try { + val inputStream: InputStream = ByteArrayInputStream(bodyResponse.toByteArray()) + val xmlParser = ExceptionParser(inputStream) + if (xmlParser.isInvalidCharacterException) { + this.code = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER + } + } catch (e: Exception) { + this.code = ResultCode.UNHANDLED_HTTP_CODE + Log_OC.e(TAG, "Exception reading exception from server", e) + } + } else { + this.code = ResultCode.UNHANDLED_HTTP_CODE + Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: $httpCode") + } + } + } + + /** + * Public constructor from exception. + * + * + * To be used when an exception prevented the end of the [RemoteOperation]. + * + * + * Determines a [ResultCode] depending on the type of the exception. + * + * @param e Exception that interrupted the [RemoteOperation] + */ + constructor(e: Exception?) { + exception = e + if (e is OperationCancelledException) { + this.code = ResultCode.CANCELLED + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && e is ErrnoException && e.errno == OsConstants.ENOTCONN) { + this.code = ResultCode.NO_NETWORK_CONNECTION + } else if (e is ConnectException) { + this.code = ResultCode.HOST_NOT_AVAILABLE + } else if (e is SocketException) { + this.code = ResultCode.WRONG_CONNECTION + } else if (e is SocketTimeoutException) { + this.code = ResultCode.TIMEOUT + } else if (e is ConnectTimeoutException) { + this.code = ResultCode.TIMEOUT + } else if (e is MalformedURLException) { + this.code = ResultCode.INCORRECT_ADDRESS + } else if (e is UnknownHostException) { + this.code = ResultCode.HOST_NOT_AVAILABLE + } else if (e is AccountUtils.AccountNotFoundException) { + this.code = ResultCode.ACCOUNT_NOT_FOUND + } else if (e is AccountsException) { + this.code = ResultCode.ACCOUNT_EXCEPTION + } else if (e is SSLException || e is RuntimeException) { + val certificateCombinedException = getCertificateCombinedException(e) + if (certificateCombinedException != null) { + exception = certificateCombinedException + if (certificateCombinedException.isRecoverable) { + this.code = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED + } + } else if (e is RuntimeException) { + this.code = ResultCode.HOST_NOT_AVAILABLE + } else { + this.code = ResultCode.SSL_ERROR + } + } else if (e is FileNotFoundException) { + this.code = ResultCode.LOCAL_FILE_NOT_FOUND + } else if (e is CreateLocalFileException) { + if (e.isCausedByInvalidPath()) { + this.code = ResultCode.INVALID_LOCAL_FILE_NAME + } else { + this.code = ResultCode.CANNOT_CREATE_FILE + } + } else { + this.code = ResultCode.UNKNOWN_ERROR + } + } + + constructor(success: Boolean, httpMethod: OkHttpMethodBase) : this( + success, + httpMethod.getStatusCode(), + httpMethod.getStatusText(), + httpMethod.getResponseHeaders() + ) + + constructor(davResponse: DavResponse) : this( + davResponse.success, + davResponse.getStatusCode(), + davResponse.status?.message ?: "", + davResponse.headers ?: Headers.headersOf() + ) + + /** + * Public constructor from separate elements of an HTTP or DAV response. + * + * + * To be used when the result needs to be interpreted from the response of an HTTP/DAV method. + * + * + * Determines a [ResultCode] from the already executed method received as a parameter. Generally, + * will depend on the HTTP code and HTTP response headers received. In some cases will inspect also the + * response body. + * + * @param success The operation was considered successful or not. + * @param httpMethod HTTP/DAV method already executed which response will be examined to interpret the + * result. + */ + constructor(success: Boolean, httpMethod: HttpMethod) : this( + success, + httpMethod.statusCode, + httpMethod.statusText, + httpMethod.responseHeaders + ) { + if (httpCode == HttpStatus.SC_BAD_REQUEST || httpCode == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE) { + try { + val bodyResponse = httpMethod.responseBodyAsString + if (!bodyResponse.isNullOrEmpty()) { + val inputStream: InputStream = ByteArrayInputStream(bodyResponse.toByteArray()) + val xmlParser = ExceptionParser(inputStream) + if (xmlParser.isInvalidCharacterException) { + this.code = ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER + } + if (xmlParser.isVirusException) { + this.code = ResultCode.VIRUS_DETECTED + } + httpPhrase = xmlParser.message + } + } catch (e: Exception) { + Log_OC.w(TAG, "Error reading exception from server: " + e.message) + // mCode stays as set in this(success, httpCode, headers) + } + } + } + + /** + * Public constructor from separate elements of an HTTP or DAV response. + * + * + * To be used when the result needs to be interpreted from HTTP response elements that could come from + * different requests (WARNING: black magic, try to avoid). + * + * + * If all the fields come from the same HTTP/DAV response, [.RemoteOperationResult] + * should be used instead. + * + * + * Determines a [ResultCode] depending on the HTTP code and HTTP response headers received. + * + * @param success The operation was considered successful or not. + * @param httpCode HTTP status code returned by an HTTP/DAV method. + * @param httpPhrase HTTP status line phrase returned by an HTTP/DAV method + * @param httpHeaders HTTP response header returned by an HTTP/DAV method + */ + constructor( + success: Boolean, + httpCode: Int, + httpPhrase: String, + httpHeaders: Array
? + ) : this(success, httpCode, httpPhrase) { + if (httpHeaders != null) { + for (httpHeader in httpHeaders) { + if (HEADER_WWW_AUTHENTICATE == httpHeader.name.lowercase()) { + authenticateHeaders.add(httpHeader.value) + } else if (HEADER_LOCATION == httpHeader.name.lowercase() && authenticateHeaders.isEmpty()) { + redirectedLocation = httpHeader.value + } + } + } + if (isIdPRedirection) { + this.code = ResultCode.UNAUTHORIZED // overrides default ResultCode.UNKNOWN + } + } + + constructor(success: Boolean, httpCode: Int, headers: Array
?) : this( + success, + httpCode + ) { + if (headers != null) { + for (header in headers) { + if (HEADER_LOCATION == header.name.lowercase()) { + redirectedLocation = header.value + } else if (HEADER_WWW_AUTHENTICATE == header.name.lowercase()) { + authenticateHeaders.add(header.value) + } + } + } + if (isIdPRedirection) { + this.code = ResultCode.UNAUTHORIZED // overrides default ResultCode.UNKNOWN + } + } + + /** + * Public constructor from separate elements of an HTTP or DAV response. + * + * + * To be used when the result needs to be interpreted from HTTP response elements that could come from + * different requests (WARNING: black magic, try to avoid). + * + * + * If all the fields come from the same HTTP/DAV response, [.RemoteOperationResult] + * should be used instead. + * + * + * Determines a [ResultCode] depending on the HTTP code and HTTP response headers received. + * + * @param success The operation was considered successful or not. + * @param httpCode HTTP status code returned by an HTTP/DAV method. + * @param httpPhrase HTTP status line phrase returned by an HTTP/DAV method + * @param httpHeaders HTTP response header returned by an HTTP/DAV method + */ + constructor( + success: Boolean, + httpCode: Int, + httpPhrase: String, + httpHeaders: Headers + ) : this(success, httpCode, httpPhrase) { + val location = httpHeaders[HEADER_LOCATION] + if (location != null) { + redirectedLocation = location + } + val auth = httpHeaders[HEADER_WWW_AUTHENTICATE] + if (auth != null) { + authenticateHeaders.add(auth) + } + if (isIdPRedirection) { + this.code = ResultCode.UNAUTHORIZED // overrides default ResultCode.UNKNOWN + } + } + + /** + * Private constructor for results built interpreting a HTTP or DAV response. + * + * + * Determines a [ResultCode] depending of the type of the exception. + * + * @param success Operation was successful or not. + * @param httpCode HTTP status code returned by the HTTP/DAV method. + * @param httpPhrase HTTP status line phrase returned by the HTTP/DAV method + */ + private constructor(success: Boolean, httpCode: Int, httpPhrase: String) { + isSuccess = success + this.httpCode = httpCode + this.httpPhrase = httpPhrase + if (success) { + this.code = ResultCode.OK + } else if (httpCode > 0) { + when (httpCode) { + HttpStatus.SC_UNAUTHORIZED -> // 401 + this.code = ResultCode.UNAUTHORIZED + + HttpStatus.SC_FORBIDDEN -> // 403 + this.code = ResultCode.FORBIDDEN + + HttpStatus.SC_NOT_FOUND -> // 404 + this.code = ResultCode.FILE_NOT_FOUND + + HttpStatus.SC_CONFLICT -> // 409 + this.code = ResultCode.CONFLICT + + HttpStatus.SC_INTERNAL_SERVER_ERROR -> // 500 + this.code = ResultCode.INSTANCE_NOT_CONFIGURED + + HttpStatus.SC_SERVICE_UNAVAILABLE -> // 503 + this.code = ResultCode.MAINTENANCE_MODE + + HttpStatus.SC_INSUFFICIENT_STORAGE -> // 507 + this.code = ResultCode.QUOTA_EXCEEDED + + else -> { + this.code = ResultCode.UNHANDLED_HTTP_CODE // UNKNOWN ERROR + Log_OC.d(TAG,"RemoteOperationResult has processed UNHANDLED_HTTP_CODE: ${this.httpCode} ${this.httpPhrase}") + } + } + } + } + + fun setResultData(data: T) { + resultData = data + } + + fun getResultData(): T? { + if (!isSuccess) { + throw RuntimeException("Accessing result data after operation failed!") + } + return resultData + } + + @get:Deprecated("use getResultData() instead") + @set:Deprecated("use setResultData() instead") + var data: java.util.ArrayList? + get() { + if (!isSuccess) { + throw RuntimeException("Accessing result data after operation failed!") + } + return if (mData != null) { + mData + } else if (resultData is ArrayList<*>) { + resultData as ArrayList<*>? + } else { + null + } + } + set(files) { + mData = files as ArrayList? + } + + @get:Deprecated("use getResultData() instead") + @set:Deprecated("use setResultData() instead") + var singleData: Any + get() { + if (!isSuccess) { + throw RuntimeException("Accessing result data after operation failed!") + } + return mData!![0] + } + set(data) { + mData = ArrayList(listOf(data)) + } + + val isCancelled = code == ResultCode.CANCELLED + + val isSslRecoverableException = code == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED + + val isRedirectToNonSecureConnection = code == ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION + + private fun getCertificateCombinedException(e: Exception): CertificateCombinedException? { + var result: CertificateCombinedException? = null + if (e is CertificateCombinedException) { + return e + } + var cause = exception!!.cause + var previousCause: Throwable? = null + while (cause != null && cause !== previousCause && + cause !is CertificateCombinedException + ) { + previousCause = cause + cause = cause.cause + } + if (cause is CertificateCombinedException) { + result = cause + } + return result + } + + val logMessage = { + exception?.let { exception -> + when (exception) { + is OperationCancelledException -> "Operation cancelled by the caller" + is SocketException -> "Socket exception" + is SocketTimeoutException -> "Socket timeout exception" + is ConnectTimeoutException -> "Connect timeout exception" + is MalformedURLException -> "Malformed URL exception" + is UnknownHostException -> "Unknown host exception" + is CertificateCombinedException -> if (exception.isRecoverable) "SSL recoverable exception" else "SSL exception" + is SSLException -> "SSL exception" + is DavException -> "Unexpected WebDAV exception" + is HttpException -> "HTTP violation" + is IOException -> "Unrecovered transport exception" + is AccountUtils.AccountNotFoundException -> "${exception.message} (${exception.failedAccount?.name ?: "NULL"})" + is AccountsException -> "Exception while using account" + is JSONException -> "JSON exception" + else -> "Unexpected exception" + } + } + when (code) { + ResultCode.INSTANCE_NOT_CONFIGURED -> "The Nextcloud server is not configured!" + ResultCode.NO_NETWORK_CONNECTION -> "No network connection" + ResultCode.BAD_OC_VERSION -> "No valid Nextcloud version was found at the server" + ResultCode.LOCAL_STORAGE_FULL -> "Local storage full" + ResultCode.LOCAL_STORAGE_NOT_MOVED -> "Error while moving file to final directory" + ResultCode.ACCOUNT_NOT_NEW -> "Account already existing when creating a new one" + ResultCode.ACCOUNT_NOT_THE_SAME -> "Authenticated with a different account than the one updating" + ResultCode.INVALID_CHARACTER_IN_NAME -> "The file name contains an forbidden character" + ResultCode.FILE_NOT_FOUND -> "Local file does not exist" + ResultCode.SYNC_CONFLICT -> "Synchronization conflict" + else -> "Operation finished with HTTP status code $httpCode (${if (isSuccess) "success" else "fail"})" + } + } + + val isServerFail = httpCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR + + fun isException() = exception != null + + val isTemporalRedirection = httpCode == 302 || httpCode == 307 + + val isIdPRedirection = redirectedLocation != null && + (redirectedLocation!!.uppercase().contains("SAML") || + redirectedLocation!!.lowercase().contains("wayf")) + + /** + * Checks if is a non https connection + */ + val isNonSecureRedirection = redirectedLocation != null && !redirectedLocation!!.lowercase() + .startsWith("https://") + + override fun toString(): String = "RemoteOperationResult{mSuccess=$isSuccess, mHttpCode=$httpCode, mHttpPhrase='$httpPhrase', mException=$exception, mCode=${this.code}, message='$message', getLogMessage='$logMessage'}" + + companion object { + // Generated - should be refreshed every time the class changes!! + @Serial + private val serialVersionUID = -4325446958558896222L + private val TAG = RemoteOperationResult::class.java.simpleName + private const val HEADER_WWW_AUTHENTICATE = "www-authenticate" + private const val HEADER_LOCATION = "location" + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.java b/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.java deleted file mode 100644 index 5b77b8906c..0000000000 --- a/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Nextcloud Android Library - * - * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2017 Mario Danic - * SPDX-License-Identifier: MIT - */ -package com.owncloud.android.lib.common.utils; - -import android.net.Uri; - -import com.owncloud.android.lib.common.network.WebdavEntry; -import com.owncloud.android.lib.resources.files.model.RemoteFile; -import com.owncloud.android.lib.resources.files.webdav.NCEtag; -import com.owncloud.android.lib.resources.files.webdav.NCFavorite; -import com.owncloud.android.lib.resources.files.webdav.NCGetLastModified; -import com.owncloud.android.lib.resources.files.webdav.NCMountType; -import com.owncloud.android.lib.resources.files.webdav.NCPermissions; -import com.owncloud.android.lib.resources.files.webdav.NCRichWorkspace; -import com.owncloud.android.lib.resources.files.webdav.NCSharee; -import com.owncloud.android.lib.resources.files.webdav.NCTags; -import com.owncloud.android.lib.resources.files.webdav.OCId; -import com.owncloud.android.lib.resources.files.webdav.OCLocalId; -import com.owncloud.android.lib.resources.files.webdav.OCOwnerDisplayName; -import com.owncloud.android.lib.resources.files.webdav.OCOwnerId; -import com.owncloud.android.lib.resources.files.webdav.OCSize; - -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.MultiStatusResponse; - -import java.util.ArrayList; -import java.util.List; - -import at.bitfire.dav4jvm.Property; -import at.bitfire.dav4jvm.Response; -import at.bitfire.dav4jvm.property.GetContentType; -import at.bitfire.dav4jvm.property.ResourceType; -import okhttp3.MediaType; - -/** - * WebDav helper. - */ -public class WebDavFileUtils { - - /** - * Read the data retrieved from the server about the contents of the target folder - * - * @param remoteData Full response got from the server with the data of the target - * folder and its direct children. - * @param filesDavUri uri to files webdav uri - * @return content of the target folder - */ - public ArrayList readData(MultiStatus remoteData, - Uri filesDavUri, - boolean isReadFolderOperation, - boolean isSearchOperation) { - ArrayList mFolderAndFiles = new ArrayList<>(); - - WebdavEntry we; - int start = 1; - - if (isReadFolderOperation) { - we = new WebdavEntry(remoteData.getResponses()[0], - filesDavUri.getEncodedPath()); - mFolderAndFiles.add(new RemoteFile(we)); - } else { - start = 0; - } - - // loop to update every child - RemoteFile remoteFile; - MultiStatusResponse[] responses = remoteData.getResponses(); - for (int i = start; i < responses.length; i++) { - /// new OCFile instance with the data from the server - we = new WebdavEntry(responses[i], filesDavUri.getEncodedPath()); - remoteFile = new RemoteFile(we); - mFolderAndFiles.add(remoteFile); - } - - return mFolderAndFiles; - } - - public ArrayList readData(List responses, Uri filesDavUri) { - ArrayList list = new ArrayList<>(); - - for (Response response : responses) { - list.add(parseResponse(response, filesDavUri)); - } - - return list; - } - - public RemoteFile parseResponse(Response response, Uri filesDavUri) { - RemoteFile remoteFile = new RemoteFile(); - String path = response.getHref().toString().split(filesDavUri.getEncodedPath(), 2)[1].replace("//", "/"); - - for (Property property : response.getProperties()) { - if (property instanceof NCEtag) { - remoteFile.setEtag(((NCEtag) property).getEtag()); - } - - if (property instanceof NCFavorite) { - remoteFile.setFavorite(((NCFavorite) property).isOcFavorite()); - } - - if (property instanceof NCGetLastModified) { - remoteFile.setModifiedTimestamp(((NCGetLastModified) property).getLastModified()); - } - - if (property instanceof GetContentType) { - MediaType type = ((GetContentType) property).getType(); - - if (type != null) { - remoteFile.setMimeType(type.toString()); - } else { - remoteFile.setMimeType(""); - } - } - - if (property instanceof ResourceType) { - if (((ResourceType) property).getTypes().contains(ResourceType.Companion.getCOLLECTION())) { - remoteFile.setMimeType(WebdavEntry.DIR_TYPE); - } - } - - if (property instanceof NCPermissions) { - remoteFile.setPermissions(((NCPermissions) property).getPermissions()); - } - - if (property instanceof OCId) { - remoteFile.setRemoteId(((OCId) property).getOcId()); - } - - if (property instanceof OCSize) { - remoteFile.setSize(((OCSize) property).getOcSize()); - } - - if (property instanceof OCLocalId) { - remoteFile.setLocalId(((OCLocalId) property).getLocalId()); - } - - if (property instanceof NCMountType) { - remoteFile.setMountType(((NCMountType) property).getType()); - } - - if (property instanceof OCOwnerId) { - remoteFile.setOwnerId(((OCOwnerId) property).getOwnerId()); - } - - if (property instanceof OCOwnerDisplayName) { - remoteFile.setOwnerDisplayName(((OCOwnerDisplayName) property).getString()); - } - - if (property instanceof NCRichWorkspace) { - remoteFile.setRichWorkspace(((NCRichWorkspace) property).getRichWorkspace()); - } - - if (property instanceof NCSharee) { - remoteFile.setSharees(((NCSharee) property).getSharees()); - } - - if (property instanceof NCTags) { - remoteFile.setTags(((NCTags) property).getTags()); - } - } - - remoteFile.setRemotePath(path); - - return remoteFile; - } -} diff --git a/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.kt b/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.kt new file mode 100644 index 0000000000..a30d797d45 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/common/utils/WebDavFileUtils.kt @@ -0,0 +1,124 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2017 Mario Danic + * SPDX-License-Identifier: MIT + */ +package com.owncloud.android.lib.common.utils + +import android.net.Uri +import at.bitfire.dav4jvm.Response +import at.bitfire.dav4jvm.property.webdav.DisplayName +import at.bitfire.dav4jvm.property.webdav.GetContentLength +import at.bitfire.dav4jvm.property.webdav.GetContentType +import at.bitfire.dav4jvm.property.webdav.ResourceType +import com.owncloud.android.lib.common.network.WebdavEntry +import com.owncloud.android.lib.resources.files.model.RemoteFile +import com.owncloud.android.lib.resources.files.webdav.NCEtag +import com.owncloud.android.lib.resources.files.webdav.NCFavorite +import com.owncloud.android.lib.resources.files.webdav.NCGetLastModified +import com.owncloud.android.lib.resources.files.webdav.NCMountType +import com.owncloud.android.lib.resources.files.webdav.NCPermissions +import com.owncloud.android.lib.resources.files.webdav.NCRichWorkspace +import com.owncloud.android.lib.resources.files.webdav.NCSharees +import com.owncloud.android.lib.resources.files.webdav.NCTags +import com.owncloud.android.lib.resources.files.webdav.OCId +import com.owncloud.android.lib.resources.files.webdav.OCLocalId +import com.owncloud.android.lib.resources.files.webdav.OCOwnerDisplayName +import com.owncloud.android.lib.resources.files.webdav.OCOwnerId +import com.owncloud.android.lib.resources.files.webdav.OCSize +import org.apache.jackrabbit.webdav.MultiStatus + +/** + * WebDav helper. + */ +object WebDavFileUtils { + /** + * Read the data retrieved from the server about the contents of the target folder + * + * @param remoteData Full response got from the server with the data of the target + * folder and its direct children. + * @param filesDavUri uri to files webdav uri + * @return content of the target folder + */ + fun readData( + remoteData: MultiStatus, + filesDavUri: Uri, + isReadFolderOperation: Boolean, + isSearchOperation: Boolean + ): ArrayList { + val mFolderAndFiles = ArrayList() + var we: WebdavEntry + var start = 1 + if (isReadFolderOperation) { + we = WebdavEntry( + remoteData.responses[0], + filesDavUri.encodedPath!! + ) + mFolderAndFiles.add(RemoteFile(we)) + } else { + start = 0 + } + + // loop to update every child + var remoteFile: RemoteFile + val responses = remoteData.responses + for (i in start until responses.size) { + /// new OCFile instance with the data from the server + we = WebdavEntry(responses[i], filesDavUri.encodedPath!!) + remoteFile = RemoteFile(we) + mFolderAndFiles.add(remoteFile) + } + return mFolderAndFiles + } + + fun readData(responses: List, filesDavUri: Uri): ArrayList { + val list = ArrayList() + for (response in responses) { + list.add(parseResponse(response, filesDavUri)) + } + return list + } + + fun parseResponse(response: Response, filesDavUri: Uri): RemoteFile { + val remoteFile = RemoteFile() + + // TODO: refactor + val path = response.href.toString().split(filesDavUri.encodedPath!!.toRegex(), limit = 2) + .toTypedArray()[1].replace("//", "/") + + for (property in response.properties) { + when (property) { + is DisplayName -> remoteFile.name = property.displayName?.apply { substring(1, length - 1) } + is NCEtag -> remoteFile.etag = property.etag + is NCFavorite -> remoteFile.isFavorite = property.favorite + is NCGetLastModified -> remoteFile.modifiedTimestamp = property.lastModified + is GetContentLength -> remoteFile.length = property.contentLength + is GetContentType -> remoteFile.mimeType = (property.type ?: "").toString() + is ResourceType -> if (property.types.contains(ResourceType.COLLECTION)) { + remoteFile.mimeType = WebdavEntry.DIR_TYPE + } + is NCPermissions -> remoteFile.permissions = property.permissions + is OCId -> remoteFile.remoteId = property.id + is OCSize -> remoteFile.size = property.size + is OCLocalId -> remoteFile.localId = property.localId + is NCMountType -> remoteFile.mountType = property.mountType + is OCOwnerId -> remoteFile.ownerId = property.ownerId + is OCOwnerDisplayName -> remoteFile.ownerDisplayName = property.ownerDisplayName + is NCRichWorkspace -> remoteFile.richWorkspace = property.richWorkspace + is NCSharees -> remoteFile.sharees = property.sharees + is NCTags -> remoteFile.tags = property.tags + } + } + + remoteFile.remotePath = path + + // displayName not set - get from path + if (remoteFile.name.isNullOrEmpty()) { + remoteFile.name = path.substringAfterLast("/") + } + + return remoteFile + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperation.java index 1652f40371..4935c6611b 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/comments/CommentFileRemoteOperation.java @@ -9,27 +9,22 @@ import android.util.Log; -import com.google.gson.GsonBuilder; -import com.owncloud.android.lib.common.OwnCloudClient; +import com.nextcloud.common.JSONRequestBody; +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.operations.PostMethod; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.StringRequestEntity; -import org.apache.commons.httpclient.methods.Utf8PostMethod; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; /** * Comment file */ -public class CommentFileRemoteOperation extends RemoteOperation { +public class CommentFileRemoteOperation extends RemoteOperation { private static final String TAG = CommentFileRemoteOperation.class.getSimpleName(); - private static final int POST_READ_TIMEOUT = 30000; - private static final int POST_CONNECTION_TIMEOUT = 5000; private static final String ACTOR_ID = "actorId"; private static final String ACTOR_TYPE = "actorType"; @@ -57,32 +52,26 @@ public CommentFileRemoteOperation(String message, long fileId) { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { + public RemoteOperationResult run(NextcloudClient client) { - Utf8PostMethod postMethod = null; - RemoteOperationResult result; + PostMethod postMethod = null; + RemoteOperationResult result; try { String url = client.getCommentsUri(fileId); - postMethod = new Utf8PostMethod(url); - postMethod.addRequestHeader("Content-type", "application/json"); - - Map values = new HashMap<>(); - values.put(ACTOR_ID, client.getUserId()); - values.put(ACTOR_TYPE, ACTOR_TYPE_VALUE); - values.put(VERB, VERB_VALUE); - values.put(MESSAGE, message); - - String json = new GsonBuilder().create().toJson(values, Map.class); + JSONRequestBody requestBody = new JSONRequestBody(); + requestBody.put(ACTOR_ID, client.getUserId()); + requestBody.put(ACTOR_TYPE, ACTOR_TYPE_VALUE); + requestBody.put(VERB, VERB_VALUE); + requestBody.put(MESSAGE, message); - postMethod.setRequestEntity(new StringRequestEntity(json)); - - int status = client.executeMethod(postMethod, POST_READ_TIMEOUT, POST_CONNECTION_TIMEOUT); + postMethod = new PostMethod(url, false, requestBody.get()); + postMethod.addRequestHeader("Content-type", "application/json"); - result = new RemoteOperationResult(isSuccess(status), postMethod); + int status = client.execute(postMethod); - client.exhaustResponse(postMethod.getResponseBodyAsStream()); + result = new RemoteOperationResult<>(isSuccess(status), postMethod); } catch (IOException e) { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); Log.e(TAG, "Post comment to file with id " + fileId + " failed: " + result.getLogMessage(), e); } finally { if (postMethod != null) { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.java deleted file mode 100644 index 34a2930c7c..0000000000 --- a/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Nextcloud Android Library - * - * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2018 Tobias Kaminsky - * SPDX-License-Identifier: MIT - */ -package com.owncloud.android.lib.resources.comments; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.network.WebdavEntry; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod; -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; -import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.property.DefaultDavProperty; -import org.apache.jackrabbit.webdav.xml.Namespace; - -import java.io.IOException; - -/** - * Mark all comments for a file as read - */ -public class MarkCommentsAsReadRemoteOperation extends RemoteOperation { - private static final String COMMENTS_URL = "/comments/files/"; - - private final long fileId; - - public MarkCommentsAsReadRemoteOperation(long fileId) { - this.fileId = fileId; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; - PropPatchMethod propPatchMethod = null; - - DavPropertySet newProps = new DavPropertySet(); - DavPropertyNameSet removeProperties = new DavPropertyNameSet(); - - DefaultDavProperty readMarkerProperty = new DefaultDavProperty<>("oc:readMarker", "", - Namespace.getNamespace(WebdavEntry.NAMESPACE_OC)); - newProps.add(readMarkerProperty); - - String commentsPath = client.getCommentsUri(fileId); - - try { - propPatchMethod = new PropPatchMethod(commentsPath, newProps, removeProperties); - int status = client.executeMethod(propPatchMethod); - - boolean isSuccess = status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_OK || - status == HttpStatus.SC_MULTI_STATUS; - - if (isSuccess) { - result = new RemoteOperationResult(true, status, propPatchMethod.getResponseHeaders()); - } else { - client.exhaustResponse(propPatchMethod.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status, propPatchMethod.getResponseHeaders()); - } - } catch (IOException e) { - result = new RemoteOperationResult(e); - } finally { - if (propPatchMethod != null) { - propPatchMethod.releaseConnection(); - } - } - - return result; - } -} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.kt new file mode 100644 index 0000000000..752cb2c918 --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/comments/MarkCommentsAsReadRemoteOperation.kt @@ -0,0 +1,29 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> + * SPDX-FileCopyrightText: 2018 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.owncloud.android.lib.resources.comments + +import com.nextcloud.common.NextcloudClient +import com.owncloud.android.lib.common.network.ExtendedProperties +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import okhttp3.HttpUrl.Companion.toHttpUrl + +/** + * Mark all comments for a file as read + */ +class MarkCommentsAsReadRemoteOperation(private val fileId: Long) : RemoteOperation() { + override fun run(client: NextcloudClient): RemoteOperationResult { + val url = client.getCommentsUri(fileId).toHttpUrl() + val readMarkerProperty = mapOf(Pair(ExtendedProperties.COMMENTS_READ_MARKER.toPropertyName(), "")) + val propPatchMethod = com.nextcloud.operations.PropPatchMethod(url, setProperties = readMarkerProperty) + val response = client.execute(propPatchMethod) + + return RemoteOperationResult(response) + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.java deleted file mode 100644 index 51f9dd33d4..0000000000 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Nextcloud Android Library - * - * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2018 Tobias Kaminsky - * SPDX-License-Identifier: MIT - */ -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -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.owncloud.android.lib.common.utils.Log_OC; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * Check if file is up to date, by checking only eTag - */ -public class CheckEtagRemoteOperation extends RemoteOperation { - - private static final int SYNC_READ_TIMEOUT = 40000; - private static final int SYNC_CONNECTION_TIMEOUT = 5000; - private static final String TAG = CheckEtagRemoteOperation.class.getSimpleName(); - - private String path; - private String expectedEtag; - - public CheckEtagRemoteOperation(String path, String expectedEtag) { - this.path = path; - this.expectedEtag = expectedEtag; - } - - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - PropFindMethod propfind = null; - - try { - DavPropertyNameSet propSet = new DavPropertyNameSet(); - propSet.add(DavPropertyName.GETETAG); - - propfind = new PropFindMethod(client.getFilesDavUri(path), - propSet, - 0); - int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); - - if (status == HttpStatus.SC_MULTI_STATUS || status == HttpStatus.SC_OK) { - MultiStatusResponse resp = propfind.getResponseBodyAsMultiStatus().getResponses()[0]; - - String etag = WebdavUtils.parseEtag((String) resp.getProperties(HttpStatus.SC_OK) - .get(DavPropertyName.GETETAG).getValue()); - - if (etag.equals(expectedEtag)) { - return new RemoteOperationResult(ResultCode.ETAG_UNCHANGED); - } else { - RemoteOperationResult result = new RemoteOperationResult(ResultCode.ETAG_CHANGED); - - ArrayList list = new ArrayList<>(); - list.add(etag); - result.setData(list); - - return result; - } - } - - if (status == HttpStatus.SC_NOT_FOUND) { - return new RemoteOperationResult(ResultCode.FILE_NOT_FOUND); - } - } catch (DavException | IOException e) { - Log_OC.e(TAG, "Error while retrieving eTag"); - } finally { - if (propfind != null) { - propfind.releaseConnection(); - } - } - - return new RemoteOperationResult(ResultCode.ETAG_CHANGED); - } -} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.kt new file mode 100644 index 0000000000..f83ee6c1ec --- /dev/null +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/CheckEtagRemoteOperation.kt @@ -0,0 +1,45 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2018 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.owncloud.android.lib.resources.files + +import com.nextcloud.common.NextcloudClient +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import com.owncloud.android.lib.resources.files.webdav.NCEtag +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.apache.commons.httpclient.HttpStatus + +/** + * Check if file is up to date, by checking only eTag + */ +class CheckEtagRemoteOperation(private val path: String, private val expectedEtag: String) : + RemoteOperation() { + override fun run(client: NextcloudClient): RemoteOperationResult { + val url = client.getFilesDavUri(path).toHttpUrl() + val propertySet = arrayOf(NCEtag.NAME) + val propFindMethod = com.nextcloud.operations.PropFindMethod(url, propertySet, 0) + val propFindResult = client.execute(propFindMethod) + + // TODO: refactor + return if (propFindResult.davResponse.success) { + val etag = propFindResult.root.etag + if (etag == expectedEtag) { + RemoteOperationResult(ResultCode.ETAG_UNCHANGED) + } else { + val result = RemoteOperationResult(ResultCode.ETAG_CHANGED) + result.setResultData(etag) + result + } + } else if (propFindResult.davResponse.getStatusCode() == HttpStatus.SC_NOT_FOUND) { + RemoteOperationResult(ResultCode.FILE_NOT_FOUND) + } else { + RemoteOperationResult(ResultCode.ETAG_CHANGED) + } + } +} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ChunkedFileUploadRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ChunkedFileUploadRemoteOperation.java index 818c4e0e4a..a46e84cee4 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ChunkedFileUploadRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ChunkedFileUploadRemoteOperation.java @@ -8,6 +8,9 @@ import android.text.TextUtils; +import androidx.annotation.VisibleForTesting; + +import com.nextcloud.extensions.ArrayExtensionsKt; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.network.ChunkFromFileChannelRequestEntity; import com.owncloud.android.lib.common.network.ProgressiveDataTransfer; @@ -34,8 +37,6 @@ import java.util.Locale; import java.util.Objects; -import androidx.annotation.VisibleForTesting; - public class ChunkedFileUploadRemoteOperation extends UploadFileRemoteOperation { @@ -128,8 +129,8 @@ protected static Chunk calcNextChunk(long fileSize, int chunkId, long startByte, } @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result; DefaultHttpMethodRetryHandler oldRetryHandler = (DefaultHttpMethodRetryHandler) client.getParams() .getParameter(HttpMethodParams.RETRY_HANDLER); File file = new File(localPath); @@ -151,7 +152,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { uploadFolderUri = client.getUploadUri() + "/" + client.getUserId() + "/" + FileUtils.md5Sum(file); - destinationUri = client.getDavUri() + "/files/" + client.getUserId() + WebdavUtils.encodePath(remotePath); + destinationUri = client.getDavUri() + "/files/" + client.getUserId() + WebdavUtils.INSTANCE.encodePath(remotePath); // create folder MkColMethod createFolder = new MkColMethod(uploadFolderUri); @@ -161,14 +162,16 @@ protected RemoteOperationResult run(OwnCloudClient client) { client.executeMethod(createFolder, 30000, 5000); // list chunks - PropFindMethod listChunks = new PropFindMethod(uploadFolderUri, - WebdavUtils.getChunksPropSet(), - DavConstants.DEPTH_1); + PropFindMethod listChunks = new PropFindMethod( + uploadFolderUri, + ArrayExtensionsKt.toLegacyPropset(WebdavUtils.PROPERTYSETS.INSTANCE.getCHUNK()), + DavConstants.DEPTH_1 + ); client.executeMethod(listChunks); if (!listChunks.succeeded()) { - return new RemoteOperationResult(listChunks.succeeded(), listChunks); + return new RemoteOperationResult<>(listChunks.succeeded(), listChunks); } MultiStatus dataInServer = listChunks.getResponseBodyAsMultiStatus(); @@ -198,13 +201,13 @@ protected RemoteOperationResult run(OwnCloudClient client) { // determine size of next chunk Chunk chunk = calcNextChunk(file.length(), ++lastId, nextByte, chunkSize); - RemoteOperationResult chunkResult = uploadChunk(client, chunk); + RemoteOperationResult chunkResult = uploadChunk(client, chunk); if (!chunkResult.isSuccess()) { return chunkResult; } if (cancellationRequested.get()) { - return new RemoteOperationResult(new OperationCancelledException()); + return new RemoteOperationResult<>(new OperationCancelledException()); } nextByte += chunk.getLength(); @@ -227,22 +230,22 @@ protected RemoteOperationResult run(OwnCloudClient client) { final int DO_NOT_CHANGE_DEFAULT = -1; int moveResult = client.executeMethod(moveMethod, calculateAssembleTimeout(file), DO_NOT_CHANGE_DEFAULT); - result = new RemoteOperationResult(isSuccess(moveResult), moveMethod); + result = new RemoteOperationResult<>(isSuccess(moveResult), moveMethod); } catch (Exception e) { if (putMethod != null && putMethod.isAborted()) { if (cancellationRequested.get() && cancellationReason != null) { - result = new RemoteOperationResult(cancellationReason); + result = new RemoteOperationResult<>(cancellationReason); } else { - result = new RemoteOperationResult(new OperationCancelledException()); + result = new RemoteOperationResult<>(new OperationCancelledException()); } } else if (moveMethod != null && moveMethod.isAborted()) { if (cancellationRequested.get() && cancellationReason != null) { - result = new RemoteOperationResult(cancellationReason); + result = new RemoteOperationResult<>(cancellationReason); } else { - result = new RemoteOperationResult(new OperationCancelledException()); + result = new RemoteOperationResult<>(new OperationCancelledException()); } } else { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); } } finally { if (disableRetries) { @@ -253,9 +256,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { return result; } - private RemoteOperationResult uploadChunk(OwnCloudClient client, Chunk chunk) throws IOException { + private RemoteOperationResult uploadChunk(OwnCloudClient client, Chunk chunk) throws IOException { int status; - RemoteOperationResult result; + RemoteOperationResult result; FileChannel channel = null; RandomAccessFile raf = null; @@ -293,7 +296,7 @@ private RemoteOperationResult uploadChunk(OwnCloudClient client, Chunk chunk) th status = client.executeMethod(putMethod); - result = new RemoteOperationResult(isSuccess(status), putMethod); + result = new RemoteOperationResult<>(isSuccess(status), putMethod); client.exhaustResponse(putMethod.getResponseBodyAsStream()); Log_OC.d(TAG, diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/CreateFolderRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/CreateFolderRemoteOperation.java index 328378d880..fbaf333f80 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/CreateFolderRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/CreateFolderRemoteOperation.java @@ -8,14 +8,16 @@ import android.text.TextUtils; -import com.owncloud.android.lib.common.OwnCloudClient; +import com.nextcloud.common.DavResponse; +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.operations.MkColMethod; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; -import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.client.methods.MkColMethod; + +import okhttp3.HttpUrl; /** @@ -28,12 +30,8 @@ public class CreateFolderRemoteOperation extends RemoteOperation { private static final String TAG = CreateFolderRemoteOperation.class.getSimpleName(); - private static final int READ_TIMEOUT = 30000; - private static final int CONNECTION_TIMEOUT = 5000; - - - private boolean createFullPath; - private String remotePath; + private final boolean createFullPath; + private final String remotePath; private String token; /** @@ -59,13 +57,13 @@ public CreateFolderRemoteOperation(String remotePath, boolean createFullPath, St * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { + public RemoteOperationResult run(NextcloudClient client) { RemoteOperationResult result; result = createFolder(client); if (!result.isSuccess() && createFullPath && RemoteOperationResult.ResultCode.CONFLICT == result.getCode() && - !"/".equals(remotePath)) { // this must already exists + !"/".equals(remotePath)) { // this must already exist result = createParentFolder(FileUtils.getParentPath(remotePath), client); if (result.isSuccess()) { result = createFolder(client); // second (and last) try @@ -76,47 +74,37 @@ protected RemoteOperationResult run(OwnCloudClient client) { } - private RemoteOperationResult createFolder(OwnCloudClient client) { + private RemoteOperationResult createFolder(NextcloudClient client) { RemoteOperationResult result; - MkColMethod mkCol = null; + try { - mkCol = new MkColMethod(client.getFilesDavUri(remotePath)); + HttpUrl url = HttpUrl.get(client.getFilesDavUri(remotePath)); + com.nextcloud.operations.MkColMethod mkCol = new MkColMethod(url); if (!TextUtils.isEmpty(token)) { mkCol.addRequestHeader(E2E_TOKEN, token); } - client.executeMethod(mkCol, READ_TIMEOUT, CONNECTION_TIMEOUT); + DavResponse response = client.execute(mkCol); - if (HttpStatus.SC_METHOD_NOT_ALLOWED == mkCol.getStatusCode()) { + if (HttpStatus.SC_METHOD_NOT_ALLOWED == response.getStatusCode()) { result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS); } else { - result = new RemoteOperationResult<>(mkCol.succeeded(), mkCol); - Header fileIdHeader = mkCol.getResponseHeader("OC-FileId"); - - if (fileIdHeader != null) { - String fileId = fileIdHeader.getValue(); - - result.setResultData(fileId); - } else { - result.setResultData(null); - } + result = new RemoteOperationResult<>(response); + String fileIdHeader = response.getHeader("OC-FileId"); + result.setResultData(fileIdHeader); } Log_OC.d(TAG, "Create directory " + remotePath + ": " + result.getLogMessage()); - client.exhaustResponse(mkCol.getResponseBodyAsStream()); } catch (Exception e) { result = new RemoteOperationResult<>(e); Log_OC.e(TAG, "Create directory " + remotePath + ": " + result.getLogMessage(), e); - - } finally { - if (mkCol != null) - mkCol.releaseConnection(); } + return result; } - private RemoteOperationResult createParentFolder(String parentPath, OwnCloudClient client) { + private RemoteOperationResult createParentFolder(String parentPath, NextcloudClient client) { RemoteOperation operation = new CreateFolderRemoteOperation(parentPath, createFullPath); return operation.execute(client); } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/DownloadFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/DownloadFileRemoteOperation.java index b5de6ebf99..488c43b8c7 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/DownloadFileRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/DownloadFileRemoteOperation.java @@ -142,13 +142,13 @@ private int downloadFile(OwnCloudClient client, File targetFile) throws IOExcept modificationTime = getMethod.getResponseHeader("last-modified"); } if (modificationTime != null) { - Date d = WebdavUtils.parseResponseDate(modificationTime.getValue()); + Date d = WebdavUtils.INSTANCE.parseResponseDate(modificationTime.getValue()); modificationTimestamp = (d != null) ? d.getTime() : 0; } else { Log_OC.e(TAG, "Could not read modification time from response downloading " + remotePath); } - eTag = WebdavUtils.getEtagFromResponse(getMethod); + eTag = WebdavUtils.INSTANCE.getEtagFromResponse(getMethod); if (eTag.length() == 0) { Log_OC.e(TAG, "Could not read eTag from response downloading " + remotePath); } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt index fdf2b48179..0783839c44 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/LinkLivePhotoRemoteOperation.kt @@ -2,22 +2,17 @@ * Nextcloud Android Library * * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> * SPDX-FileCopyrightText: 2018 Tobias Kaminsky * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.files -import com.owncloud.android.lib.common.OwnCloudClient -import com.owncloud.android.lib.common.network.WebdavEntry +import com.nextcloud.common.NextcloudClient +import com.owncloud.android.lib.common.network.ExtendedProperties import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult -import org.apache.commons.httpclient.HttpStatus -import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet -import org.apache.jackrabbit.webdav.property.DavPropertySet -import org.apache.jackrabbit.webdav.property.DefaultDavProperty -import org.apache.jackrabbit.webdav.xml.Namespace -import java.io.IOException +import okhttp3.HttpUrl.Companion.toHttpUrl /** * Links live photos @@ -26,38 +21,12 @@ class LinkLivePhotoRemoteOperation( private val path: String, private val linkedFileName: String ) : RemoteOperation() { - @Deprecated("Deprecated in Java") - override fun run(client: OwnCloudClient): RemoteOperationResult { - var result: RemoteOperationResult - lateinit var propPatchMethod: PropPatchMethod - val newProps = DavPropertySet() - val removeProperties = DavPropertyNameSet() - val readMarkerProperty = - DefaultDavProperty( - "nc:metadata-files-live-photo", - linkedFileName, - Namespace.getNamespace(WebdavEntry.NAMESPACE_NC) - ) - newProps.add(readMarkerProperty) + override fun run(client: NextcloudClient): RemoteOperationResult { + val url = client.getFilesDavUri(path).toHttpUrl() + val metadataLiveProperty = mapOf(Pair(ExtendedProperties.METADATA_LIVE_PHOTO.toPropertyName(), linkedFileName)) + val propPatchMethod = com.nextcloud.operations.PropPatchMethod(url, setProperties = metadataLiveProperty) + val response = client.execute(propPatchMethod) - val commentsPath = client.getFilesDavUri(path) - try { - propPatchMethod = PropPatchMethod(commentsPath, newProps, removeProperties) - val status = client.executeMethod(propPatchMethod) - val isSuccess = - status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_OK || status == HttpStatus.SC_MULTI_STATUS - result = - if (isSuccess) { - RemoteOperationResult(true, status, propPatchMethod.responseHeaders) - } else { - client.exhaustResponse(propPatchMethod.responseBodyAsStream) - RemoteOperationResult(false, status, propPatchMethod.responseHeaders) - } - } catch (e: IOException) { - result = RemoteOperationResult(e) - } finally { - propPatchMethod.releaseConnection() - } - return result + return RemoteOperationResult(response) } } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/NcSearchMethod.java b/library/src/main/java/com/owncloud/android/lib/resources/files/NCSearchMethod.java similarity index 89% rename from library/src/main/java/com/owncloud/android/lib/resources/files/NcSearchMethod.java rename to library/src/main/java/com/owncloud/android/lib/resources/files/NCSearchMethod.java index 347d612d18..eb7538603a 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/NcSearchMethod.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/NCSearchMethod.java @@ -7,9 +7,8 @@ */ package com.owncloud.android.lib.resources.files; -import static com.owncloud.android.lib.common.network.WebdavEntry.EXTENDED_PROPERTY_IS_ENCRYPTED; -import static com.owncloud.android.lib.common.network.WebdavEntry.NAMESPACE_NC; -import static com.owncloud.android.lib.common.network.WebdavEntry.NAMESPACE_OC; +import static com.owncloud.android.lib.common.network.WebdavUtils.NAMESPACE_NC; +import static com.owncloud.android.lib.common.network.WebdavUtils.NAMESPACE_OC; import com.owncloud.android.lib.resources.status.NextcloudVersion; import com.owncloud.android.lib.resources.status.OCCapability; @@ -30,7 +29,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -public class NcSearchMethod extends org.apache.jackrabbit.webdav.client.methods.SearchMethod { +public class NCSearchMethod extends org.apache.jackrabbit.webdav.client.methods.SearchMethod { private static final String HEADER_CONTENT_TYPE_VALUE = "text/xml"; private static final String DAV_NAMESPACE = "DAV:"; @@ -44,7 +43,7 @@ public class NcSearchMethod extends org.apache.jackrabbit.webdav.client.methods. private final Long endDate; @SuppressWarnings("PMD.ExcessiveParameterList") - public NcSearchMethod(String uri, + public NCSearchMethod(String uri, SearchInfo searchInfo, SearchRemoteOperation.SearchType searchType, String userId, @@ -107,7 +106,7 @@ private Document createQuery(String searchQuery) { Element sizeElement = query.createElementNS(NAMESPACE_OC, "oc:size"); Element favoriteElement = query.createElementNS(NAMESPACE_OC, "oc:favorite"); Element previewElement = query.createElementNS(NAMESPACE_OC, "nc:has-preview"); - Element encryptedElement = query.createElementNS(NAMESPACE_NC, EXTENDED_PROPERTY_IS_ENCRYPTED); + Element encryptedElement = query.createElementNS(NAMESPACE_NC, "nc:is-encrypted"); if (searchType != SearchRemoteOperation.SearchType.GALLERY_SEARCH) { selectPropsElement.appendChild(displayNameElement); @@ -137,26 +136,12 @@ private Document createQuery(String searchQuery) { Text depthTextElement = query.createTextNode("infinity"); Element whereElement = query.createElementNS(DAV_NAMESPACE, "d:where"); Element folderElement; - Element equalsElement; - - switch (searchType) { - case FAVORITE_SEARCH: - case FILE_ID_SEARCH: - equalsElement = query.createElementNS(DAV_NAMESPACE, "d:eq"); - break; - - case RECENTLY_MODIFIED_SEARCH: - equalsElement = query.createElementNS(DAV_NAMESPACE, "d:gt"); - break; - - case GALLERY_SEARCH: - equalsElement = query.createElementNS(DAV_NAMESPACE, "d:or"); - break; - - default: - equalsElement = query.createElementNS(DAV_NAMESPACE, "d:like"); - break; - } + Element equalsElement = switch (searchType) { + case FAVORITE_SEARCH, FILE_ID_SEARCH -> query.createElementNS(DAV_NAMESPACE, "d:eq"); + case RECENTLY_MODIFIED_SEARCH -> query.createElementNS(DAV_NAMESPACE, "d:gt"); + case GALLERY_SEARCH -> query.createElementNS(DAV_NAMESPACE, "d:or"); + default -> query.createElementNS(DAV_NAMESPACE, "d:like"); + }; Element propElement = null; Element queryElement = null; @@ -169,29 +154,19 @@ private Document createQuery(String searchQuery) { propElement = query.createElementNS(DAV_NAMESPACE, "d:prop"); switch (searchType) { - case PHOTO_SEARCH: - queryElement = query.createElementNS(DAV_NAMESPACE, "d:getcontenttype"); - break; - - case FILE_SEARCH: - queryElement = query.createElementNS(DAV_NAMESPACE, "d:displayname"); - break; - - case FAVORITE_SEARCH: - queryElement = query.createElementNS(NAMESPACE_OC, "oc:favorite"); - break; - - case RECENTLY_MODIFIED_SEARCH: - queryElement = query.createElementNS(DAV_NAMESPACE, "d:getlastmodified"); - break; - - case FILE_ID_SEARCH: - queryElement = query.createElementNS(NAMESPACE_OC, "oc:fileid"); - break; - - default: - // no default - break; + case PHOTO_SEARCH -> + queryElement = query.createElementNS(DAV_NAMESPACE, "d:getcontenttype"); + case FILE_SEARCH -> + queryElement = query.createElementNS(DAV_NAMESPACE, "d:displayname"); + case FAVORITE_SEARCH -> + queryElement = query.createElementNS(NAMESPACE_OC, "oc:favorite"); + case RECENTLY_MODIFIED_SEARCH -> + queryElement = query.createElementNS(DAV_NAMESPACE, "d:getlastmodified"); + case FILE_ID_SEARCH -> + queryElement = query.createElementNS(NAMESPACE_OC, "oc:fileid"); + default -> { + } + // no default } literalElement = query.createElementNS(DAV_NAMESPACE, "d:literal"); diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java index bf350ac93d..31eb734100 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileRemoteOperation.java @@ -6,20 +6,15 @@ */ package com.owncloud.android.lib.resources.files; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.network.WebdavEntry; +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.operations.PropFindResult; 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.utils.Log_OC; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - -import java.util.ArrayList; +import okhttp3.HttpUrl; /** @@ -29,13 +24,11 @@ * @author masensio */ -public class ReadFileRemoteOperation extends RemoteOperation { +public class ReadFileRemoteOperation extends RemoteOperation { private static final String TAG = ReadFileRemoteOperation.class.getSimpleName(); - private static final int SYNC_READ_TIMEOUT = 40000; - private static final int SYNC_CONNECTION_TIMEOUT = 5000; - private String mRemotePath; + private final String mRemotePath; /** @@ -53,49 +46,25 @@ public ReadFileRemoteOperation(String remotePath) { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { - PropFindMethod propfind = null; - RemoteOperationResult result = null; + public RemoteOperationResult run(NextcloudClient client) { + com.nextcloud.operations.PropFindMethod propFind; + RemoteOperationResult result; - /// take the duty of check the server for the current state of the file there try { // remote request - propfind = new PropFindMethod(client.getFilesDavUri(mRemotePath), - WebdavUtils.getFilePropSet(), // PropFind Properties - DavConstants.DEPTH_0); - int status; - status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); - - boolean isSuccess = ( - status == HttpStatus.SC_MULTI_STATUS || - status == HttpStatus.SC_OK - ); - if (isSuccess) { - // Parse response - MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], - client.getFilesDavUri().getEncodedPath()); - RemoteFile remoteFile = new RemoteFile(we); - ArrayList files = new ArrayList(); - files.add(remoteFile); + HttpUrl url = HttpUrl.get(client.getFilesDavUri(mRemotePath)); + propFind = new com.nextcloud.operations.PropFindMethod(url, WebdavUtils.PROPERTYSETS.INSTANCE.getFILE(), 0); + PropFindResult propFindResult = client.execute(propFind); - // Result of the operation - result = new RemoteOperationResult(true, propfind); - result.setData(files); - - } else { - result = new RemoteOperationResult(false, propfind); - client.exhaustResponse(propfind.getResponseBodyAsStream()); - } + result = new RemoteOperationResult<>(propFindResult.getDavResponse()); + result.setResultData(propFindResult.getRoot()); } catch (Exception e) { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); Log_OC.e(TAG, "Read file " + mRemotePath + " failed: " + result.getLogMessage(), result.getException()); - } finally { - if (propfind != null) - propfind.releaseConnection(); } + return result; } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperation.java index c18a6beece..d00f178176 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFileVersionsRemoteOperation.java @@ -7,6 +7,7 @@ */ package com.owncloud.android.lib.resources.files; +import com.nextcloud.extensions.ArrayExtensionsKt; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.network.WebdavUtils; @@ -55,7 +56,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { try { String uri = client.getDavUri() + "/versions/" + client.getUserId() + "/versions/" + localId; - DavPropertyNameSet propSet = WebdavUtils.getFileVersionPropSet(); + DavPropertyNameSet propSet = ArrayExtensionsKt.toLegacyPropset( + WebdavUtils.PROPERTYSETS.INSTANCE.getFILE_VERSION() + ); query = new PropFindMethod(uri, propSet, DavConstants.DEPTH_1); int status = client.executeMethod(query); diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFolderRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFolderRemoteOperation.java index c4a67c4247..1e7e76b39b 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFolderRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ReadFolderRemoteOperation.java @@ -6,20 +6,17 @@ */ package com.owncloud.android.lib.resources.files; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.network.WebdavEntry; +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.operations.PropFindResult; 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.utils.Log_OC; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +import java.util.List; -import java.util.ArrayList; +import okhttp3.HttpUrl; /** * Remote operation performing the read of remote file or folder in the ownCloud server. @@ -28,12 +25,11 @@ * @author masensio */ -public class ReadFolderRemoteOperation extends RemoteOperation { +public class ReadFolderRemoteOperation extends RemoteOperation> { private static final String TAG = ReadFolderRemoteOperation.class.getSimpleName(); - private String mRemotePath; - private ArrayList mFolderAndFiles; + private final String mRemotePath; /** * Constructor @@ -50,44 +46,23 @@ public ReadFolderRemoteOperation(String remotePath) { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result = null; - PropFindMethod query = null; + public RemoteOperationResult> run(NextcloudClient client) { + RemoteOperationResult> result = null; + com.nextcloud.operations.PropFindMethod propFind; try { // remote request - query = new PropFindMethod(client.getFilesDavUri(mRemotePath), - WebdavUtils.getAllPropSet(), // PropFind Properties - DavConstants.DEPTH_1); - int status = client.executeMethod(query); - - // check and process response - boolean isSuccess = (status == HttpStatus.SC_MULTI_STATUS || status == HttpStatus.SC_OK); - - if (isSuccess) { - // get data from remote folder - MultiStatus dataInServer = query.getResponseBodyAsMultiStatus(); - readData(dataInServer, client); - - // Result of the operation - result = new RemoteOperationResult(true, query); - // Add data to the result - if (result.isSuccess()) { - result.setData(mFolderAndFiles); - } - } else { - // synchronization failed - client.exhaustResponse(query.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, query); - } + HttpUrl url = HttpUrl.get(client.getFilesDavUri(mRemotePath)); + propFind = new com.nextcloud.operations.PropFindMethod(url, + WebdavUtils.PROPERTYSETS.INSTANCE.getALL(), 1); + PropFindResult propFindResult = client.execute(propFind); + result = new RemoteOperationResult<>(propFindResult.getDavResponse()); + result.setResultData(propFindResult.getChildren()); } catch (Exception e) { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); } finally { - if (query != null) - query.releaseConnection(); // let the connection available for other methods - if (result == null) { - result = new RemoteOperationResult(new Exception("unknown error")); + result = new RemoteOperationResult<>(new Exception("unknown error")); Log_OC.e(TAG, "Synchronized " + mRemotePath + ": failed"); } else { if (result.isSuccess()) { @@ -102,38 +77,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } } } - - return result; - } - - public boolean isMultiStatus(int status) { - return (status == HttpStatus.SC_MULTI_STATUS); - } - - /** - * Read the data retrieved from the server about the contents of the target folder - * - * @param remoteData Full response got from the server with the data of the target - * folder and its direct children. - * @param client Client instance to the remote server where the data were - * retrieved. - * @return - */ - private void readData(MultiStatus remoteData, OwnCloudClient client) { - mFolderAndFiles = new ArrayList<>(); - - // parse data from remote folder - WebdavEntry we = new WebdavEntry(remoteData.getResponses()[0], client.getFilesDavUri().getEncodedPath()); - mFolderAndFiles.add(new RemoteFile(we)); - - // loop to update every child - RemoteFile remoteFile; - for (int i = 1; i < remoteData.getResponses().length; ++i) { - /// new OCFile instance with the data from the server - we = new WebdavEntry(remoteData.getResponses()[i], client.getFilesDavUri().getEncodedPath()); - remoteFile = new RemoteFile(we); - mFolderAndFiles.add(remoteFile); - } + return result; } } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/SearchRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/SearchRemoteOperation.java index b8d5ae20bb..ce7245e98c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/SearchRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/SearchRemoteOperation.java @@ -97,14 +97,14 @@ public void setEndDate(Long endDate) { @Override protected RemoteOperationResult> run(OwnCloudClient client) { RemoteOperationResult> result; - NcSearchMethod searchMethod = null; + NCSearchMethod searchMethod = null; OptionsMethod optionsMethod; String webDavUrl = client.getDavUri().toString(); optionsMethod = new OptionsMethod(webDavUrl); try { - searchMethod = new NcSearchMethod(webDavUrl, + searchMethod = new NCSearchMethod(webDavUrl, new SearchInfo("NC", Namespace.XMLNS_NAMESPACE, searchQuery), @@ -125,11 +125,12 @@ protected RemoteOperationResult> run(OwnCloudClient client) { if (isSuccess) { // get data from remote folder MultiStatus dataInServer = searchMethod.getResponseBodyAsMultiStatus(); - WebDavFileUtils webDavFileUtils = new WebDavFileUtils(); - ArrayList mFolderAndFiles = webDavFileUtils.readData(dataInServer, + ArrayList mFolderAndFiles = WebDavFileUtils.INSTANCE.readData( + dataInServer, client.getFilesDavUri(), false, - true); + true + ); // Result of the operation result = new RemoteOperationResult<>(true, status, searchMethod.getResponseHeaders()); @@ -155,9 +156,9 @@ protected RemoteOperationResult> run(OwnCloudClient client) { } @Override - public RemoteOperationResult run(NextcloudClient client) { + public RemoteOperationResult> run(NextcloudClient client) { RemoteOperationResult> result; - NcSearchMethod searchMethod = null; + NCSearchMethod searchMethod = null; String webDavUrl = client.getDavUri().toString(); @@ -166,7 +167,7 @@ public RemoteOperationResult run(NextcloudClient client) { searchQuery); try { - searchMethod = new NcSearchMethod(webDavUrl, + searchMethod = new NCSearchMethod(webDavUrl, searchInfo, searchType, client.getUserIdPlain(), @@ -185,16 +186,12 @@ public RemoteOperationResult run(NextcloudClient client) { new DavResource( client.disabledRedirectClient(), HttpUrl.get(client.getDavUri().toString())) - .search(searchString, (response, hrefRelation) -> { - responses.add(response); - }); + .search(searchString, (response, hrefRelation) -> responses.add(response)); // get data from remote folder - WebDavFileUtils webDavFileUtils = new WebDavFileUtils(); - ArrayList list = new ArrayList<>(); for (Response response : responses) { - list.add(webDavFileUtils.parseResponse(response, client.getFilesDavUri())); + list.add(WebDavFileUtils.INSTANCE.parseResponse(response, client.getFilesDavUri())); } // Result of the operation diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ToggleFavoriteRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ToggleFavoriteRemoteOperation.java index 43ad3fb992..0aeb220667 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ToggleFavoriteRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ToggleFavoriteRemoteOperation.java @@ -11,7 +11,7 @@ import com.nextcloud.common.NextcloudClient; import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.network.WebdavEntry; +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.resources.files.webdav.NCFavorite; @@ -56,10 +56,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (makeItFavorited) { DefaultDavProperty favoriteProperty = new DefaultDavProperty<>("oc:favorite", "1", - Namespace.getNamespace(WebdavEntry.NAMESPACE_OC)); + Namespace.getNamespace(WebdavUtils.NAMESPACE_OC)); newProps.add(favoriteProperty); } else { - removeProperties.add("oc:favorite", Namespace.getNamespace(WebdavEntry.NAMESPACE_OC)); + removeProperties.add("oc:favorite", Namespace.getNamespace(WebdavUtils.NAMESPACE_OC)); } String webDavUrl = client.getDavUri().toString(); diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt b/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt index 8edea5fe43..9cbb8a3e4b 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/model/RemoteFile.kt @@ -8,6 +8,7 @@ package com.owncloud.android.lib.resources.files.model import android.os.Parcel import android.os.Parcelable +import com.nextcloud.extensions.readParcelableArrayBridge import com.owncloud.android.lib.common.network.WebdavEntry import com.owncloud.android.lib.common.network.WebdavEntry.MountType import com.owncloud.android.lib.resources.files.FileUtils @@ -39,8 +40,9 @@ class RemoteFile : Parcelable, Serializable { var ownerDisplayName: String? = null var unreadCommentsCount = 0 var isHasPreview = false + var name: String? = null var note: String? = null - var sharees: Array? = null + var sharees: Array? = null var richWorkspace: String? = null var isLocked = false var lockType: FileLockType? = null @@ -50,7 +52,7 @@ class RemoteFile : Parcelable, Serializable { var lockOwnerEditor: String? = null var lockTimeout: Long = 0 var lockToken: String? = null - var tags: Array? = null + var tags: Array? = null var imageDimension: ImageDimension? = null var geoLocation: GeoLocation? = null var hidden = false @@ -92,6 +94,7 @@ class RemoteFile : Parcelable, Serializable { mountType = we.mountType ownerId = we.ownerId ownerDisplayName = we.ownerDisplayName + name = we.name note = we.note unreadCommentsCount = we.unreadCommentsCount isHasPreview = we.isHasPreview @@ -130,6 +133,7 @@ class RemoteFile : Parcelable, Serializable { isEncrypted = false ownerId = "" ownerDisplayName = "" + name = "" note = "" isLocked = false lockOwner = null @@ -170,8 +174,9 @@ class RemoteFile : Parcelable, Serializable { ownerId = source.readString() ownerDisplayName = source.readString() isHasPreview = source.readString().toBoolean() + name = source.readString() note = source.readString() - source.readParcelableArray(ShareeUser::class.java.classLoader) + source.readParcelableArrayBridge(ShareeUser::class.java) isLocked = source.readInt() == 1 lockType = fromValue(source.readInt()) lockOwner = source.readString() @@ -208,6 +213,7 @@ class RemoteFile : Parcelable, Serializable { dest.writeString(ownerId) dest.writeString(ownerDisplayName) dest.writeString(isHasPreview.toString()) + dest.writeString(name) dest.writeString(note) dest.writeParcelableArray(sharees, 0) dest.writeInt(if (isLocked) 1 else 0) @@ -222,6 +228,13 @@ class RemoteFile : Parcelable, Serializable { dest.writeInt(if (hidden) 1 else 0) } + /** + * Check whether RemoteFile is a directory. + * + * @return `true`, iff RemoteFile is directory. + */ + fun isDirectory() = mimeType == "DIR" + companion object { /** * Generated - should be refreshed every time the class changes!! diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareToRemoteOperationResultParser.java b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareToRemoteOperationResultParser.java index cfe57a75d2..b116140348 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareToRemoteOperationResultParser.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareToRemoteOperationResultParser.java @@ -91,16 +91,16 @@ public RemoteOperationResult> parse(String serverResponse) { } else if (shareXmlParser.isWrongParameter()) { result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_WRONG_PARAMETER); - result.setMessage(shareXmlParser.getMessage()); + result.message = shareXmlParser.getMessage(); } else if (shareXmlParser.isNotFound()) { result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); - result.setMessage(shareXmlParser.getMessage()); + result.message = shareXmlParser.getMessage(); } else if (shareXmlParser.isForbidden()) { result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.SHARE_FORBIDDEN); - result.setMessage(shareXmlParser.getMessage()); + result.message = shareXmlParser.getMessage(); } else { result = new RemoteOperationResult<>(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE); - result.setMessage(shareXmlParser.getMessage()); + result.message = shareXmlParser.getMessage(); } } catch (XmlPullParserException e) { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java index ad7d08c49f..2fa8d21218 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/shares/ShareXMLParser.java @@ -346,7 +346,7 @@ private void readElement(XmlPullParser parser, ArrayList shares) case NODE_EXPIRATION: String expirationValue = readNode(parser, NODE_EXPIRATION); if (expirationValue.length() > 0) { - Date date = WebdavUtils.parseResponseDate(expirationValue); + Date date = WebdavUtils.INSTANCE.parseResponseDate(expirationValue); if (date != null) { share.setExpirationDate(date.getTime()); } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperation.kt index 22577c82f2..15097a023d 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/tags/GetTagsRemoteOperation.kt @@ -2,67 +2,41 @@ * Nextcloud Android Library * * SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com> * SPDX-FileCopyrightText: 2023 Tobias Kaminsky * SPDX-License-Identifier: MIT */ package com.owncloud.android.lib.resources.tags -import com.owncloud.android.lib.common.OwnCloudClient -import com.owncloud.android.lib.common.network.WebdavEntry.Companion.EXTENDED_PROPERTY_NAME_REMOTE_ID -import com.owncloud.android.lib.common.network.WebdavEntry.Companion.NAMESPACE_OC -import com.owncloud.android.lib.common.network.WebdavEntry.Companion.SHAREES_DISPLAY_NAME +import com.nextcloud.common.NextcloudClient +import com.owncloud.android.lib.common.network.ExtendedProperties import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult -import org.apache.commons.httpclient.HttpStatus -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet -import org.apache.jackrabbit.webdav.xml.Namespace +import okhttp3.HttpUrl.Companion.toHttpUrl class GetTagsRemoteOperation : RemoteOperation>() { @Deprecated("Deprecated in Java") - override fun run(client: OwnCloudClient): RemoteOperationResult> { - val ocNamespace = Namespace.getNamespace(NAMESPACE_OC) + override fun run(client: NextcloudClient): RemoteOperationResult> { + val url = (client.baseUri.toString() + TAG_URL).toHttpUrl() - val propSet = - DavPropertyNameSet().apply { - add(EXTENDED_PROPERTY_NAME_REMOTE_ID, ocNamespace) - add(SHAREES_DISPLAY_NAME, ocNamespace) - } - - val propFindMethod = - PropFindMethod( - client.baseUri.toString() + TAG_URL, - propSet, - 1 - ) - - val status = client.executeMethod(propFindMethod) - - return if (status == HttpStatus.SC_MULTI_STATUS) { - val response = propFindMethod.responseBodyAsMultiStatus.responses + val propertySet = arrayOf( + ExtendedProperties.NAME_REMOTE_ID.toPropertyName(), + ExtendedProperties.SHAREES_DISPLAY_NAME.toPropertyName() + ) - val result = mutableListOf() - response.forEach { - if (it.getProperties(HttpStatus.SC_OK).contentSize > 0) { - val id = - it.getProperties(HttpStatus.SC_OK) - .get(EXTENDED_PROPERTY_NAME_REMOTE_ID, ocNamespace).value as String - val name = - it.getProperties(HttpStatus.SC_OK) - .get(SHAREES_DISPLAY_NAME, ocNamespace).value as String + val propFindMethod = com.nextcloud.operations.PropFindMethod(url, propertySet, 1) + val propFindResult = client.execute(propFindMethod) + val result = RemoteOperationResult>(propFindResult.davResponse) - result.add(Tag(id, name)) - } + val tags = propFindResult.root.remoteId?.let { id -> + propFindResult.root.tags?.map { + Tag(id, it) } + } ?: emptyList() - RemoteOperationResult>(true, propFindMethod).apply { - resultData = result - } - } else { - RemoteOperationResult>(false, propFindMethod).apply { - resultData = emptyList() - } - } + result.setResultData(tags) + + return result } companion object { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/ReadTrashbinFolderRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/ReadTrashbinFolderRemoteOperation.java index 98948ab6ac..dda34a7456 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/ReadTrashbinFolderRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/ReadTrashbinFolderRemoteOperation.java @@ -7,6 +7,7 @@ */ package com.owncloud.android.lib.resources.trashbin; +import com.nextcloud.extensions.ArrayExtensionsKt; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.network.WebdavUtils; @@ -55,9 +56,11 @@ public RemoteOperationResult> run(OwnCloudClient client) { try { String baseUri = client.getDavUri() + "/trashbin/" + client.getUserId() + "/trash"; - DavPropertyNameSet propSet = WebdavUtils.getTrashbinPropSet(); + DavPropertyNameSet propSet = ArrayExtensionsKt.toLegacyPropset( + WebdavUtils.PROPERTYSETS.INSTANCE.getTRASHBIN() + ); - query = new PropFindMethod(baseUri + WebdavUtils.encodePath(remotePath), propSet, DavConstants.DEPTH_1); + query = new PropFindMethod(baseUri + WebdavUtils.INSTANCE.encodePath(remotePath), propSet, DavConstants.DEPTH_1); int status = client.executeMethod(query); // check and process response diff --git a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RemoveTrashbinFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RemoveTrashbinFileRemoteOperation.java index d6b3259986..14df65a22c 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RemoveTrashbinFileRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RemoveTrashbinFileRemoteOperation.java @@ -19,13 +19,13 @@ /** * Remote operation performing the removal of a file in trashbin. */ -public class RemoveTrashbinFileRemoteOperation extends RemoteOperation { +public class RemoveTrashbinFileRemoteOperation extends RemoteOperation { private static final String TAG = RemoveTrashbinFileRemoteOperation.class.getSimpleName(); private static final int REMOVE_READ_TIMEOUT = 30000; private static final int REMOVE_CONNECTION_TIMEOUT = 5000; - private String remotePath; + private final String remotePath; /** * Constructor @@ -42,20 +42,20 @@ public RemoveTrashbinFileRemoteOperation(String remotePath) { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result; DeleteMethod delete = null; try { - delete = new DeleteMethod(client.getDavUri() + WebdavUtils.encodePath(remotePath)); + delete = new DeleteMethod(client.getDavUri() + WebdavUtils.INSTANCE.encodePath(remotePath)); int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT); delete.getResponseBodyAsString(); // exhaust the response, although not interesting - result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), delete); + result = new RemoteOperationResult<>((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), delete); Log_OC.i(TAG, "Remove " + remotePath + ": " + result.getLogMessage()); } catch (Exception e) { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e); } finally { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RestoreTrashbinFileRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RestoreTrashbinFileRemoteOperation.java index a1455f5a66..2f3b1f4ce8 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RestoreTrashbinFileRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/trashbin/RestoreTrashbinFileRemoteOperation.java @@ -24,14 +24,14 @@ /** * Restore a {@link TrashbinFile}. */ -public class RestoreTrashbinFileRemoteOperation extends RemoteOperation { +public class RestoreTrashbinFileRemoteOperation extends RemoteOperation { private static final String TAG = RestoreTrashbinFileRemoteOperation.class.getSimpleName(); private static final int RESTORE_READ_TIMEOUT = 30000; private static final int RESTORE_CONNECTION_TIMEOUT = 5000; - private String sourcePath; - private String fileName; + private final String sourcePath; + private final String fileName; /** * Constructor @@ -50,23 +50,23 @@ public RestoreTrashbinFileRemoteOperation(String sourcePath, String fileName) { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(OwnCloudClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { MoveMethod move = null; - RemoteOperationResult result; + RemoteOperationResult result; try { - String source = client.getDavUri() + WebdavUtils.encodePath(sourcePath); + String source = client.getDavUri() + WebdavUtils.INSTANCE.encodePath(sourcePath); String target = client.getDavUri() + "/trashbin/" + client.getUserId() + "/restore/" + Uri.encode(fileName); move = new MoveMethod(source, target, true); int status = client.executeMethod(move, RESTORE_READ_TIMEOUT, RESTORE_CONNECTION_TIMEOUT); - result = new RemoteOperationResult(isSuccess(status), move); + result = new RemoteOperationResult<>(isSuccess(status), move); client.exhaustResponse(move.getResponseBodyAsStream()); } catch (IOException e) { - result = new RemoteOperationResult(e); + result = new RemoteOperationResult<>(e); Log.e(TAG, "Restore trashbin file " + sourcePath + " failed: " + result.getLogMessage(), e); } finally { if (move != null) { diff --git a/library/src/main/java/com/owncloud/android/lib/resources/users/GetUserAvatarRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/users/GetUserAvatarRemoteOperation.java index f3dffe20f8..ee063ecb8d 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/users/GetUserAvatarRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/users/GetUserAvatarRemoteOperation.java @@ -153,7 +153,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { private RemoteOperationResult createResult(GetMethod get, byte[] avatarData, String mimeType) throws IOException { // find out etag - String etag = WebdavUtils.getEtagFromResponse(get); + String etag = WebdavUtils.INSTANCE.getEtagFromResponse(get); if (etag.length() == 0) { Log_OC.w(TAG, "Could not read Etag from avatar"); }