diff --git a/owncloudComLibrary/build.gradle b/owncloudComLibrary/build.gradle index cfcd453d..34391930 100644 --- a/owncloudComLibrary/build.gradle +++ b/owncloudComLibrary/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' dependencies { api 'com.squareup.okhttp3:okhttp:4.6.0' @@ -16,7 +17,7 @@ dependencies { kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" testImplementation 'junit:junit:4.13.2' - testImplementation 'org.robolectric:robolectric:4.8.1' + testImplementation 'org.robolectric:robolectric:4.9' debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java index e332b8fa..1281ec13 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java @@ -36,7 +36,6 @@ import com.owncloud.android.lib.common.http.HttpClient; import com.owncloud.android.lib.common.http.HttpConstants; import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; import com.owncloud.android.lib.common.utils.RandomUtils; -import com.owncloud.android.lib.resources.status.OwnCloudVersion; import okhttp3.Cookie; import okhttp3.HttpUrl; import timber.log.Timber; @@ -59,7 +58,6 @@ public class OwnCloudClient extends HttpClient { private OwnCloudCredentials mCredentials = null; private int mInstanceNumber; private Uri mBaseUri; - private OwnCloudVersion mVersion = null; private OwnCloudAccount mAccount; private final ConnectionValidator mConnectionValidator; private Object mRequestMutex = new Object(); @@ -185,7 +183,7 @@ public class OwnCloudClient extends HttpClient { return (mCredentials instanceof OwnCloudAnonymousCredentials || mAccount == null) ? Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0) : Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0 + AccountUtils.getUserId( - mAccount.getSavedAccount(), getContext() + mAccount.getSavedAccount(), getContext() ) ); } @@ -194,7 +192,7 @@ public class OwnCloudClient extends HttpClient { return mCredentials instanceof OwnCloudAnonymousCredentials ? Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0) : Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0 + AccountUtils.getUserId( - mAccount.getSavedAccount(), getContext() + mAccount.getSavedAccount(), getContext() ) ); } @@ -241,14 +239,6 @@ public class OwnCloudClient extends HttpClient { HttpUrl.parse(mBaseUri.toString())); } - public OwnCloudVersion getOwnCloudVersion() { - return mVersion; - } - - public void setOwnCloudVersion(OwnCloudVersion version) { - mVersion = version; - } - public OwnCloudAccount getAccount() { return mAccount; } @@ -260,4 +250,4 @@ public class OwnCloudClient extends HttpClient { public void setFollowRedirects(boolean followRedirects) { this.mFollowRedirects = followRedirects; } -} \ No newline at end of file +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java index 9db87f4b..535104df 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -94,26 +94,6 @@ public class AccountUtils { return username; } - /** - * Get the stored server version corresponding to an OC account. - * - * @param account An OC account - * @param context Application context - * @return Version of the OC server, according to last check - */ - public static OwnCloudVersion getServerVersionForAccount(Account account, Context context) { - AccountManager ama = AccountManager.get(context); - OwnCloudVersion version = null; - try { - String versionString = ama.getUserData(account, Constants.KEY_OC_VERSION); - version = new OwnCloudVersion(versionString); - - } catch (Exception e) { - Timber.e(e, "Couldn't get a the server version for an account"); - } - return version; - } - /** * @return * @throws IOException @@ -209,11 +189,6 @@ public class AccountUtils { } public static class Constants { - /** - * Version should be 3 numbers separated by dot so it can be parsed by - * {@link OwnCloudVersion} - */ - public static final String KEY_OC_VERSION = "oc_version"; /** * Base url should point to owncloud installation without trailing / ie: * http://server/path or https://owncloud.server diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java index 091643ab..8978a556 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java @@ -184,6 +184,7 @@ public class HttpConstants { public static final int HTTP_LOCKED = 423; // 424 Failed Dependency (WebDAV - RFC 2518) public static final int HTTP_FAILED_DEPENDENCY = 424; + public static final int HTTP_TOO_EARLY = 425; /** * 5xx Client Error diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/CopyMethod.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/CopyMethod.kt index d10718e0..217f3ca9 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/CopyMethod.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/CopyMethod.kt @@ -36,7 +36,7 @@ import java.net.URL class CopyMethod( val url: URL, private val destinationUrl: String, - private val forceOverride: Boolean + private val forceOverride: Boolean = false ) : DavMethod(url) { @Throws(Exception::class) public override fun onDavExecute(davResource: DavOCResource): Int { diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/MoveMethod.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/MoveMethod.kt index bc6d9d3e..8cb4c0e9 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/MoveMethod.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/MoveMethod.kt @@ -36,7 +36,7 @@ import java.net.URL class MoveMethod( url: URL, private val destinationUrl: String, - private val forceOverride: Boolean + private val forceOverride: Boolean = false ) : DavMethod(url) { @Throws(Exception::class) override fun onDavExecute(davResource: DavOCResource): Int { diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/PropfindMethod.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/PropfindMethod.kt index 6bce3472..6f987f1d 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/PropfindMethod.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/methods/webdav/PropfindMethod.kt @@ -53,7 +53,7 @@ class PropfindMethod( depth = depth, reqProp = propertiesToRequest, listOfHeaders = super.getRequestHeadersAsHashMap(), - callback = { response: Response, hrefRelation: HrefRelation? -> + callback = { response: Response, hrefRelation: HrefRelation -> when (hrefRelation) { HrefRelation.MEMBER -> members.add(response) HrefRelation.SELF -> this.root = response diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.java deleted file mode 100644 index a43b2c37..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.java +++ /dev/null @@ -1,114 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.common.network; - -import okhttp3.MediaType; -import okio.BufferedSink; -import timber.log.Timber; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.Iterator; - -/** - * A Request body that represents a file chunk and include information about the progress when uploading it - * - * @author David González Verdugo - */ -public class ChunkFromFileRequestBody extends FileRequestBody { - - private final FileChannel mChannel; - private final long mChunkSize; - private long mOffset; - private long mTransferred; - private ByteBuffer mBuffer = ByteBuffer.allocate(4096); - - public ChunkFromFileRequestBody(File file, MediaType contentType, FileChannel channel, long chunkSize) { - super(file, contentType); - if (channel == null) { - throw new IllegalArgumentException("File may not be null"); - } - if (chunkSize <= 0) { - throw new IllegalArgumentException("Chunk size must be greater than zero"); - } - this.mChannel = channel; - this.mChunkSize = chunkSize; - mOffset = 0; - mTransferred = 0; - } - - @Override - public long contentLength() { - try { - return Math.min(mChunkSize, mChannel.size() - mChannel.position()); - } catch (IOException e) { - return mChunkSize; - } - } - - @Override - public void writeTo(BufferedSink sink) { - int readCount; - Iterator it; - - try { - mChannel.position(mOffset); - long size = mFile.length(); - if (size == 0) { - size = -1; - } - long maxCount = Math.min(mOffset + mChunkSize, mChannel.size()); - while (mChannel.position() < maxCount) { - - readCount = mChannel.read(mBuffer); - - int bytesToWriteInBuffer = (int) Math.min(readCount, mFile.length() - mTransferred); - sink.getBuffer().write(mBuffer.array(), 0, bytesToWriteInBuffer); - - sink.flush(); - - mBuffer.clear(); - if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks - mTransferred += readCount; - } - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readCount, mTransferred, size, mFile.getAbsolutePath()); - } - } - } - - } catch (Exception exception) { - Timber.e(exception, "Transferred " + mTransferred + " bytes from a total of " + mFile.length()); - } - } - - public void setOffset(long offset) { - this.mOffset = offset; - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.kt new file mode 100644 index 00000000..b1404b9c --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ChunkFromFileRequestBody.kt @@ -0,0 +1,92 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.common.network + +import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSystemOperation.Companion.CHUNK_SIZE +import okhttp3.MediaType +import okio.BufferedSink +import timber.log.Timber +import java.io.File +import java.nio.ByteBuffer +import java.nio.channels.FileChannel + +/** + * A Request body that represents a file chunk and include information about the progress when uploading it + * + * @author David González Verdugo + */ +class ChunkFromFileRequestBody( + file: File, + contentType: MediaType?, + private val channel: FileChannel, + private val chunkSize: Long = CHUNK_SIZE +) : FileRequestBody(file, contentType) { + + private var offset: Long = 0 + private var alreadyTransferred: Long = 0 + private val buffer = ByteBuffer.allocate(4_096) + + init { + require(chunkSize > 0) { "Chunk size must be greater than zero" } + } + + override fun contentLength(): Long { + return chunkSize.coerceAtMost(channel.size() - channel.position()) + } + + override fun writeTo(sink: BufferedSink) { + var readCount: Int + var iterator: Iterator + try { + channel.position(offset) + + val maxCount = (offset + chunkSize).coerceAtMost(channel.size()) + while (channel.position() < maxCount) { + readCount = channel.read(buffer) + val bytesToWriteInBuffer = readCount.toLong().coerceAtMost(file.length() - alreadyTransferred).toInt() + sink.buffer.write(buffer.array(), 0, bytesToWriteInBuffer) + sink.flush() + buffer.clear() + + if (alreadyTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks + alreadyTransferred += readCount.toLong() + } + + synchronized(dataTransferListeners) { + iterator = dataTransferListeners.iterator() + while (iterator.hasNext()) { + iterator.next().onTransferProgress(readCount.toLong(), alreadyTransferred, file.length(), file.absolutePath) + } + } + } + } catch (exception: Exception) { + Timber.e(exception, "Transferred " + alreadyTransferred + " bytes from a total of " + file.length()) + } + } + + fun setOffset(newOffset: Long) { + offset = newOffset + } + +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ContentUriRequestBody.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ContentUriRequestBody.kt new file mode 100644 index 00000000..ca4e31a9 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/ContentUriRequestBody.kt @@ -0,0 +1,117 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network + +import android.content.ContentResolver +import android.net.Uri +import android.provider.OpenableColumns +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody +import okio.BufferedSink +import okio.Source +import okio.source +import timber.log.Timber +import java.io.IOException + +class ContentUriRequestBody( + private val contentResolver: ContentResolver, + private val contentUri: Uri +) : RequestBody(), ProgressiveDataTransferer { + + private val dataTransferListeners: MutableSet = HashSet() + + val fileSize: Long = contentResolver.query(contentUri, null, null, null, null)?.use { cursor -> + val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) + cursor.moveToFirst() + cursor.getLong(sizeIndex) + } ?: -1 + + override fun contentType(): MediaType? { + val contentType = contentResolver.getType(contentUri) ?: return null + return contentType.toMediaTypeOrNull() + } + + override fun contentLength(): Long { + return fileSize + } + + override fun writeTo(sink: BufferedSink) { + val inputStream = contentResolver.openInputStream(contentUri) + ?: throw IOException("Couldn't open content URI for reading: $contentUri") + + val previousTime = System.currentTimeMillis() + + sink.writeAndUpdateProgress(inputStream.source()) + inputStream.source().close() + + val laterTime = System.currentTimeMillis() + + Timber.d("Difference - ${laterTime - previousTime} milliseconds") + } + + private fun BufferedSink.writeAndUpdateProgress(source: Source) { + var iterator: Iterator + + try { + var totalBytesRead = 0L + var read: Long + while (source.read(this.buffer, BYTES_TO_READ).also { read = it } != -1L) { + totalBytesRead += read + this.flush() + synchronized(dataTransferListeners) { + iterator = dataTransferListeners.iterator() + while (iterator.hasNext()) { + iterator.next().onTransferProgress(read, totalBytesRead, fileSize, contentUri.toString()) + } + } + } + } catch (e: Exception) { + Timber.e(e) + } + } + + override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListeners) { + dataTransferListeners.add(listener) + } + } + + override fun addDatatransferProgressListeners(listeners: MutableCollection) { + synchronized(dataTransferListeners) { + dataTransferListeners.addAll(listeners) + } + } + + override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListeners) { + dataTransferListeners.remove(listener) + } + } + + companion object { + private const val BYTES_TO_READ = 4_096L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.java deleted file mode 100644 index 18746ba7..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.java +++ /dev/null @@ -1,119 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.common.network; - -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okio.BufferedSink; -import okio.Okio; -import okio.Source; -import timber.log.Timber; - -import java.io.File; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * A Request body that represents a file and include information about the progress when uploading it - * - * @author David González Verdugo - */ -public class FileRequestBody extends RequestBody implements ProgressiveDataTransferer { - - final Set mDataTransferListeners = new HashSet<>(); - protected File mFile; - private MediaType mContentType; - - public FileRequestBody(File file, MediaType contentType) { - mFile = file; - mContentType = contentType; - } - - @Override - public boolean isOneShot() { - return true; - } - - @Override - public MediaType contentType() { - return mContentType; - } - - @Override - public long contentLength() { - return mFile.length(); - } - - @Override - public void writeTo(BufferedSink sink) { - Source source; - Iterator it; - try { - source = Okio.source(mFile); - - long transferred = 0; - long read; - - while ((read = source.read(sink.buffer(), 4096)) != -1) { - transferred += read; - sink.flush(); - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(read, transferred, mFile.length(), mFile.getAbsolutePath()); - } - } - } - - Timber.d("File with name " + mFile.getName() + " and size " + mFile.length() + " written in request body"); - - } catch (Exception e) { - Timber.e(e); - } - } - - @Override - public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - @Override - public void addDatatransferProgressListeners(Collection listeners) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.addAll(listeners); - } - } - - @Override - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.kt new file mode 100644 index 00000000..fd5f8dd3 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/FileRequestBody.kt @@ -0,0 +1,97 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.common.network + +import okhttp3.MediaType +import okhttp3.RequestBody +import okio.BufferedSink +import okio.Source +import okio.source +import timber.log.Timber +import java.io.File +import java.util.HashSet + +/** + * A Request body that represents a file and include information about the progress when uploading it + * + * @author David González Verdugo + */ +open class FileRequestBody( + val file: File, + private val contentType: MediaType?, +) : RequestBody(), ProgressiveDataTransferer { + + val dataTransferListeners: MutableSet = HashSet() + + override fun isOneShot(): Boolean = true + + override fun contentType(): MediaType? = contentType + + override fun contentLength(): Long = file.length() + + override fun writeTo(sink: BufferedSink) { + val source: Source + var it: Iterator + try { + source = file.source() + var transferred: Long = 0 + var read: Long + while (source.read(sink.buffer, BYTES_TO_READ).also { read = it } != -1L) { + transferred += read + sink.flush() + synchronized(dataTransferListeners) { + it = dataTransferListeners.iterator() + while (it.hasNext()) { + it.next().onTransferProgress(read, transferred, file.length(), file.absolutePath) + } + } + } + Timber.d("File with name ${file.name} and size ${file.length()} written in request body") + } catch (e: Exception) { + Timber.e(e) + } + } + + override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListeners) { + dataTransferListeners.add(listener) + } + } + + override fun addDatatransferProgressListeners(listeners: Collection) { + synchronized(dataTransferListeners) { + dataTransferListeners.addAll(listeners) + } + } + + override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListeners) { + dataTransferListeners.remove(listener) + } + } + + companion object { + private const val BYTES_TO_READ = 4_096L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index 7430771d..5d849b08 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -237,6 +237,10 @@ public class RemoteOperationResult httpMethod.getResponseBodyAsString(), ResultCode.SPECIFIC_METHOD_NOT_ALLOWED ); + break; + case HttpConstants.HTTP_TOO_EARLY: + mCode = ResultCode.TOO_EARLY; + break; default: break; } @@ -583,6 +587,7 @@ public class RemoteOperationResult SPECIFIC_SERVICE_UNAVAILABLE, SPECIFIC_UNSUPPORTED_MEDIA_TYPE, SPECIFIC_METHOD_NOT_ALLOWED, - SPECIFIC_BAD_REQUEST + SPECIFIC_BAD_REQUEST, + TOO_EARLY, } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/CreateRemoteChunkFolderOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/utils/AnyExt.kt similarity index 60% rename from owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/CreateRemoteChunkFolderOperation.java rename to owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/utils/AnyExt.kt index fc2d797f..462ca975 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/CreateRemoteChunkFolderOperation.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/utils/AnyExt.kt @@ -22,24 +22,8 @@ * */ -package com.owncloud.android.lib.resources.files.chunks; +package com.owncloud.android.lib.common.utils -import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; - -/** - * Remote operation performing the creation of a new folder to save chunks during an upload to the ownCloud server. - * - * @author David González Verdugo - */ -public class CreateRemoteChunkFolderOperation extends CreateRemoteFolderOperation { - /** - * Constructor - * - * @param remotePath Full path to the new directory to create in the remote server. - * @param createFullPath 'True' means that all the ancestor folders should be created. - */ - public CreateRemoteChunkFolderOperation(String remotePath, boolean createFullPath) { - super(remotePath, createFullPath); - createChunksFolder = true; - } -} \ No newline at end of file +fun Any.isOneOf(vararg values: Any): Boolean { + return this in values +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/CommonOcsResponse.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/CommonOcsResponse.kt index 7a37dc82..321d06f6 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/CommonOcsResponse.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/CommonOcsResponse.kt @@ -36,7 +36,7 @@ data class CommonOcsResponse( @JsonClass(generateAdapter = true) data class OCSResponse( val meta: MetaData, - val data: T + val data: T? ) @JsonClass(generateAdapter = true) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.java deleted file mode 100644 index 9c78425a..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.java +++ /dev/null @@ -1,130 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod; -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 timber.log.Timber; - -import java.net.URL; -import java.util.concurrent.TimeUnit; - -/** - * Remote operation moving a remote file or folder in the ownCloud server to a different folder - * in the same account. - * - * Allows renaming the moving file/folder at the same time. - * - * @author David A. Velasco - * @author Christian Schabesberger - * @author David González V. - */ -public class CopyRemoteFileOperation extends RemoteOperation { - - private static final int COPY_READ_TIMEOUT = 600000; - private static final int COPY_CONNECTION_TIMEOUT = 5000; - - private String mSrcRemotePath; - private String mTargetRemotePath; - - private boolean mOverwrite; - - /** - * Constructor. - *

- * TODO Paths should finish in "/" in the case of folders. ? - * - * @param srcRemotePath Remote path of the file/folder to move. - * @param targetRemotePath Remove path desired for the file/folder after moving it. - */ - public CopyRemoteFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite - ) { - mSrcRemotePath = srcRemotePath; - mTargetRemotePath = targetRemotePath; - mOverwrite = overwrite; - } - - /** - * Performs the rename operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - - if (mTargetRemotePath.equals(mSrcRemotePath)) { - // nothing to do! - return new RemoteOperationResult<>(ResultCode.OK); - } - - if (mTargetRemotePath.startsWith(mSrcRemotePath)) { - return new RemoteOperationResult<>(ResultCode.INVALID_COPY_INTO_DESCENDANT); - } - - /// perform remote operation - RemoteOperationResult result; - try { - CopyMethod copyMethod = - new CopyMethod( - new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mSrcRemotePath)), - client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath), - mOverwrite); - - copyMethod.setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS); - copyMethod.setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS); - - final int status = client.executeHttpMethod(copyMethod); - - if (status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT) { - String fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID); - result = new RemoteOperationResult<>(ResultCode.OK); - result.setData(fileRemoteId); - } else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) { - result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE); - client.exhaustResponse(copyMethod.getResponseBodyAsStream()); - - /// for other errors that could be explicitly handled, check first: - /// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4 - } else { - - result = new RemoteOperationResult<>(copyMethod); - client.exhaustResponse(copyMethod.getResponseBodyAsStream()); - } - - Timber.i("Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage()); - } - - return result; - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.kt new file mode 100644 index 00000000..ce47a4fd --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CopyRemoteFileOperation.kt @@ -0,0 +1,114 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod +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.isOneOf +import timber.log.Timber +import java.net.URL +import java.util.concurrent.TimeUnit + +/** + * Remote operation copying a remote file or folder in the ownCloud server to a different folder + * in the same account. + * + * Allows renaming the copying file/folder at the same time. + * + * @author David A. Velasco + * @author Christian Schabesberger + * @author David González V. + * + * @param srcRemotePath Remote path of the file/folder to copy. + * @param targetRemotePath Remote path desired for the file/folder to copy it. + */ +class CopyRemoteFileOperation( + private val srcRemotePath: String, + private val targetRemotePath: String, +) : RemoteOperation() { + /** + * Performs the rename operation. + * + * @param client Client object to communicate with the remote ownCloud server. + */ + override fun run(client: OwnCloudClient): RemoteOperationResult { + if (targetRemotePath == srcRemotePath) { + // nothing to do! + return RemoteOperationResult(ResultCode.OK) + } + if (targetRemotePath.startsWith(srcRemotePath)) { + return RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT) + } + + /// perform remote operation + var result: RemoteOperationResult + try { + val copyMethod = CopyMethod( + URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(srcRemotePath)), + client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(targetRemotePath), + ).apply { + setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS) + setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) + } + val status = client.executeHttpMethod(copyMethod) + when { + isSuccess(status) -> { + val fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID) + result = RemoteOperationResult(ResultCode.OK) + result.setData(fileRemoteId) + } + isPreconditionFailed(status) -> { + result = RemoteOperationResult(ResultCode.INVALID_OVERWRITE) + client.exhaustResponse(copyMethod.getResponseBodyAsStream()) + + /// for other errors that could be explicitly handled, check first: + /// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4 + } + else -> { + result = RemoteOperationResult(copyMethod) + client.exhaustResponse(copyMethod.getResponseBodyAsStream()) + } + } + Timber.i("Copy $srcRemotePath to $targetRemotePath: ${result.logMessage}") + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Copy $srcRemotePath to $targetRemotePath: ${result.logMessage}") + } + return result + } + + private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) + + private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED + + companion object { + private const val COPY_READ_TIMEOUT = 10L + private const val COPY_CONNECTION_TIMEOUT = 6L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.java deleted file mode 100644 index 302df20d..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.java +++ /dev/null @@ -1,114 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2019 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import android.net.Uri; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod; -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 timber.log.Timber; - -import java.net.URL; -import java.util.concurrent.TimeUnit; - -/** - * Remote operation performing the creation of a new folder in the ownCloud server. - * - * @author David A. Velasco - * @author masensio - */ -public class CreateRemoteFolderOperation extends RemoteOperation { - - private static final int READ_TIMEOUT = 30000; - private static final int CONNECTION_TIMEOUT = 5000; - - private String mRemotePath; - private boolean mCreateFullPath; - protected boolean createChunksFolder; - - /** - * Constructor - * - * @param remotePath Full path to the new directory to create in the remote server. - * @param createFullPath 'True' means that all the ancestor folders should be created. - */ - public CreateRemoteFolderOperation(String remotePath, boolean createFullPath) { - mRemotePath = remotePath; - mCreateFullPath = createFullPath; - createChunksFolder = false; - } - - /** - * Performs the operation - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result = createFolder(client); - if (!result.isSuccess() && mCreateFullPath && - RemoteOperationResult.ResultCode.CONFLICT == result.getCode()) { - result = createParentFolder(FileUtils.getParentPath(mRemotePath), client); - if (result.isSuccess()) { - result = createFolder(client); // second (and last) try - } - } - return result; - } - - private RemoteOperationResult createFolder(OwnCloudClient client) { - RemoteOperationResult result; - try { - Uri webDavUri = createChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri(); - final MkColMethod mkcol = new MkColMethod( - new URL(webDavUri + WebdavUtils.encodePath(mRemotePath))); - mkcol.setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS); - mkcol.setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS); - final int status = client.executeHttpMethod(mkcol); - - result = (status == HttpConstants.HTTP_CREATED) - ? new RemoteOperationResult<>(ResultCode.OK) - : new RemoteOperationResult<>(mkcol); - Timber.d("Create directory " + mRemotePath + ": " + result.getLogMessage()); - client.exhaustResponse(mkcol.getResponseBodyAsStream()); - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Create directory " + mRemotePath + ": " + result.getLogMessage()); - } - - return result; - } - - private RemoteOperationResult createParentFolder(String parentPath, OwnCloudClient client) { - RemoteOperation operation = new CreateRemoteFolderOperation(parentPath, mCreateFullPath); - return operation.execute(client); - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.kt new file mode 100644 index 00000000..372c8f21 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/CreateRemoteFolderOperation.kt @@ -0,0 +1,109 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2020 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod +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 timber.log.Timber +import java.net.URL +import java.util.concurrent.TimeUnit + +/** + * Remote operation performing the creation of a new folder in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + * + * @param remotePath Full path to the new directory to create in the remote server. + * @param createFullPath 'True' means that all the ancestor folders should be created. + */ +class CreateRemoteFolderOperation( + val remotePath: String, + private val createFullPath: Boolean, + private val isChunksFolder: Boolean = false +) : RemoteOperation() { + + override fun run(client: OwnCloudClient): RemoteOperationResult { + + var result = createFolder(client) + if (!result.isSuccess && createFullPath && result.code == ResultCode.CONFLICT) { + result = createParentFolder(FileUtils.getParentPath(remotePath), client) + + if (result.isSuccess) { + // Second and last try + result = createFolder(client) + } + } + return result + } + + private fun createFolder(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val webDavUri = if (isChunksFolder) { + client.uploadsWebDavUri + } else { + client.userFilesWebDavUri + } + + val mkCol = MkColMethod( + URL(webDavUri.toString() + WebdavUtils.encodePath(remotePath)) + ).apply { + setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS) + } + + val status = client.executeHttpMethod(mkCol) + result = + if (status == HttpConstants.HTTP_CREATED) { + RemoteOperationResult(ResultCode.OK) + } else { + RemoteOperationResult(mkCol) + } + + Timber.d("Create directory $remotePath: ${result.logMessage}") + client.exhaustResponse(mkCol.getResponseBodyAsStream()) + + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Create directory $remotePath: ${result.logMessage}") + } + return result + } + + private fun createParentFolder(parentPath: String, client: OwnCloudClient): RemoteOperationResult { + val operation: RemoteOperation = CreateRemoteFolderOperation(parentPath, createFullPath) + return operation.execute(client) + } + + companion object { + private const val READ_TIMEOUT: Long = 30_000 + private const val CONNECTION_TIMEOUT: Long = 5_000 + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.java deleted file mode 100644 index 51ba6b3e..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.java +++ /dev/null @@ -1,221 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod; -import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; -import com.owncloud.android.lib.common.network.WebdavUtils; -import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import timber.log.Timber; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.net.URL; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Remote operation performing the download of a remote file in the ownCloud server. - * - * @author David A. Velasco - * @author masensio - */ - -public class DownloadRemoteFileOperation extends RemoteOperation { - - private static final int FORBIDDEN_ERROR = 403; - private static final int SERVICE_UNAVAILABLE_ERROR = 503; - private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - private Set mDataTransferListeners = new HashSet<>(); - private long mModificationTimestamp = 0; - private String mEtag = ""; - private GetMethod mGet; - - private String mRemotePath; - private String mLocalFolderPath; - - public DownloadRemoteFileOperation(String remotePath, String localFolderPath) { - mRemotePath = remotePath; - mLocalFolderPath = localFolderPath; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; - - /// download will be performed to a temporal file, then moved to the final location - File tmpFile = new File(getTmpPath()); - - /// perform the download - try { - tmpFile.getParentFile().mkdirs(); - result = downloadFile(client, tmpFile); - Timber.i("Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage()); - } - - return result; - } - - private RemoteOperationResult downloadFile(OwnCloudClient client, File targetFile) throws - Exception { - - RemoteOperationResult result; - int status; - boolean savedFile = false; - mGet = new GetMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath))); - Iterator it; - - FileOutputStream fos = null; - BufferedInputStream bis = null; - try { - status = client.executeHttpMethod(mGet); - if (isSuccess(status)) { - targetFile.createNewFile(); - bis = new BufferedInputStream(mGet.getResponseBodyAsStream()); - fos = new FileOutputStream(targetFile); - long transferred = 0; - - String contentLength = mGet.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER); - long totalToTransfer = - (contentLength != null - && contentLength.length() > 0) - ? Long.parseLong(contentLength) - : 0; - - byte[] bytes = new byte[4096]; - int readResult; - while ((readResult = bis.read(bytes)) != -1) { - synchronized (mCancellationRequested) { - if (mCancellationRequested.get()) { - mGet.abort(); - throw new OperationCancelledException(); - } - } - fos.write(bytes, 0, readResult); - transferred += readResult; - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readResult, transferred, totalToTransfer, - targetFile.getName()); - } - } - } - if (transferred == totalToTransfer) { // Check if the file is completed - savedFile = true; - final String modificationTime = - mGet.getResponseHeaders().get("Last-Modified") != null - ? mGet.getResponseHeaders().get("Last-Modified") - : mGet.getResponseHeader("last-modified"); - - if (modificationTime != null) { - final Date d = WebdavUtils.parseResponseDate(modificationTime); - mModificationTimestamp = (d != null) ? d.getTime() : 0; - } else { - Timber.e("Could not read modification time from response downloading %s", mRemotePath); - } - - mEtag = WebdavUtils.getEtagFromResponse(mGet); - - // Get rid of extra quotas - mEtag = mEtag.replace("\"", ""); - - if (mEtag.length() == 0) { - Timber.e("Could not read eTag from response downloading %s", mRemotePath); - } - - } else { - Timber.e("Content-Length not equal to transferred bytes."); - Timber.d("totalToTransfer = %d, transferred = %d", totalToTransfer, transferred); - client.exhaustResponse(mGet.getResponseBodyAsStream()); - // TODO some kind of error control! - } - - } else if (status != FORBIDDEN_ERROR && status != SERVICE_UNAVAILABLE_ERROR) { - client.exhaustResponse(mGet.getResponseBodyAsStream()); - - } // else, body read by RemoteOperationResult constructor - - result = isSuccess(status) - ? new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK) - : new RemoteOperationResult<>(mGet); - } finally { - if (fos != null) { - fos.close(); - } - if (bis != null) { - bis.close(); - } - if (!savedFile && targetFile.exists()) { - targetFile.delete(); - } - } - return result; - } - - private boolean isSuccess(int status) { - return (status == HttpConstants.HTTP_OK); - } - - private String getTmpPath() { - return mLocalFolderPath + mRemotePath; - } - - public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } - - public void cancel() { - mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it - } - - public long getModificationTimestamp() { - return mModificationTimestamp; - } - - public String getEtag() { - return mEtag; - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.kt new file mode 100644 index 00000000..80b9adc7 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/DownloadRemoteFileOperation.kt @@ -0,0 +1,184 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2020 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener +import com.owncloud.android.lib.common.network.WebdavUtils +import com.owncloud.android.lib.common.operations.OperationCancelledException +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import timber.log.Timber +import java.io.BufferedInputStream +import java.io.File +import java.io.FileOutputStream +import java.net.URL +import java.util.HashSet +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Remote operation performing the download of a remote file in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + */ +class DownloadRemoteFileOperation( + private val remotePath: String, + localFolderPath: String +) : RemoteOperation() { + + private val cancellationRequested = AtomicBoolean(false) + private val dataTransferListeners: MutableSet = HashSet() + + var modificationTimestamp: Long = 0 + private set + + var etag: String = "" + private set + + override fun run(client: OwnCloudClient): RemoteOperationResult { + // download will be performed to a temporal file, then moved to the final location + val tmpFile = File(tmpPath) + + // perform the download + return try { + tmpFile.parentFile?.mkdirs() + downloadFile(client, tmpFile).also { result -> + Timber.i("Download of $remotePath to $tmpPath: ${result.logMessage}") + } + } catch (e: Exception) { + RemoteOperationResult(e).also { result -> + Timber.e(e, "Download of $remotePath to $tmpPath: ${result.logMessage}") + } + } + } + + @Throws(Exception::class) + private fun downloadFile(client: OwnCloudClient, targetFile: File): RemoteOperationResult { + val result: RemoteOperationResult + var it: Iterator + var fos: FileOutputStream? = null + var bis: BufferedInputStream? = null + var savedFile = false + + val getMethod = GetMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath))) + + try { + val status = client.executeHttpMethod(getMethod) + + if (isSuccess(status)) { + targetFile.createNewFile() + bis = BufferedInputStream(getMethod.getResponseBodyAsStream()) + fos = FileOutputStream(targetFile) + var transferred: Long = 0 + val contentLength = getMethod.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER) + val totalToTransfer = if (!contentLength.isNullOrEmpty()) { + contentLength.toLong() + } else { + 0 + } + val bytes = ByteArray(4096) + var readResult: Int + while (bis.read(bytes).also { readResult = it } != -1) { + synchronized(cancellationRequested) { + if (cancellationRequested.get()) { + getMethod.abort() + throw OperationCancelledException() + } + } + fos.write(bytes, 0, readResult) + transferred += readResult.toLong() + synchronized(dataTransferListeners) { + it = dataTransferListeners.iterator() + while (it.hasNext()) { + it.next() + .onTransferProgress(readResult.toLong(), transferred, totalToTransfer, targetFile.name) + } + } + } + + if (transferred == totalToTransfer) { // Check if the file is completed + savedFile = true + val modificationTime = + getMethod.getResponseHeaders()?.get("Last-Modified") + ?: getMethod.getResponseHeader("last-modified") + + if (modificationTime != null) { + val modificationDate = WebdavUtils.parseResponseDate(modificationTime) + modificationTimestamp = modificationDate?.time ?: 0 + } else { + Timber.e("Could not read modification time from response downloading %s", remotePath) + } + etag = WebdavUtils.getEtagFromResponse(getMethod) + + // Get rid of extra quotas + etag = etag.replace("\"", "") + if (etag.isEmpty()) { + Timber.e("Could not read eTag from response downloading %s", remotePath) + } + } else { + Timber.e("Content-Length not equal to transferred bytes.") + Timber.d("totalToTransfer = $totalToTransfer, transferred = $transferred") + client.exhaustResponse(getMethod.getResponseBodyAsStream()) + // TODO some kind of error control! + } + + } else if (status != HttpConstants.HTTP_FORBIDDEN && status != HttpConstants.HTTP_SERVICE_UNAVAILABLE) { + client.exhaustResponse(getMethod.getResponseBodyAsStream()) + } // else, body read by RemoteOperationResult constructor + + result = + if (isSuccess(status)) { + RemoteOperationResult(RemoteOperationResult.ResultCode.OK) + } else { + RemoteOperationResult(getMethod) + } + } finally { + fos?.close() + bis?.close() + if (!savedFile && targetFile.exists()) { + targetFile.delete() + } + } + return result + } + + private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK + + private val tmpPath: String = localFolderPath + remotePath + + fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListeners) { dataTransferListeners.add(listener) } + } + + fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener?) { + synchronized(dataTransferListeners) { dataTransferListeners.remove(listener) } + } + + fun cancel() { + cancellationRequested.set(true) // atomic set; there is no need of synchronizing it + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/FileUtils.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/FileUtils.java index cc159611..1ac0a6fb 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/FileUtils.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/FileUtils.java @@ -32,6 +32,7 @@ public class FileUtils { public static final String FINAL_CHUNKS_FILE = ".file"; public static final String MIME_DIR = "DIR"; public static final String MIME_DIR_UNIX = "httpd/unix-directory"; + public static final String MODE_READ_ONLY = "r"; static String getParentPath(String remotePath) { String parentPath = new File(remotePath).getParent(); diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.java deleted file mode 100644 index 46181e8f..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.java +++ /dev/null @@ -1,146 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import android.net.Uri; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod; -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 timber.log.Timber; - -import java.net.URL; -import java.util.concurrent.TimeUnit; - -/** - * Remote operation moving a remote file or folder in the ownCloud server to a different folder - * in the same account. - *

- * Allows renaming the moving file/folder at the same time. - * - * @author David A. Velasco - * @author David González Verdugo - */ -public class MoveRemoteFileOperation extends RemoteOperation { - - private static final int MOVE_READ_TIMEOUT = 600000; - private static final int MOVE_CONNECTION_TIMEOUT = 5000; - - private String mSrcRemotePath; - private String mTargetRemotePath; - private boolean mOverwrite; - - protected boolean moveChunkedFile = false; - protected String mFileLastModifTimestamp; - protected long mFileLength; - - /** - * Constructor. - *

- * TODO Paths should finish in "/" in the case of folders. ? - * - * @param srcRemotePath Remote path of the file/folder to move. - * @param targetRemotePath Remote path desired for the file/folder after moving it. - */ - public MoveRemoteFileOperation(String srcRemotePath, - String targetRemotePath, - boolean overwrite) { - - mSrcRemotePath = srcRemotePath; - mTargetRemotePath = targetRemotePath; - mOverwrite = overwrite; - } - - /** - * Performs the rename operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - if (mTargetRemotePath.equals(mSrcRemotePath)) { - // nothing to do! - return new RemoteOperationResult<>(ResultCode.OK); - } - - if (mTargetRemotePath.startsWith(mSrcRemotePath)) { - return new RemoteOperationResult<>(ResultCode.INVALID_MOVE_INTO_DESCENDANT); - } - - /// perform remote operation - RemoteOperationResult result; - try { - // After finishing a chunked upload, we have to move the resulting file from uploads folder to files one, - // so this uri has to be customizable - Uri srcWebDavUri = moveChunkedFile ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri(); - - final MoveMethod move = new MoveMethod( - new URL(srcWebDavUri + WebdavUtils.encodePath(mSrcRemotePath)), - client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath), - mOverwrite); - - if (moveChunkedFile) { - move.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp); - move.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(mFileLength)); - } - - move.setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS); - move.setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS); - - final int status = client.executeHttpMethod(move); - /// process response - if (isSuccess(status)) { - result = new RemoteOperationResult<>(ResultCode.OK); - } else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) { - - result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE); - client.exhaustResponse(move.getResponseBodyAsStream()); - - /// for other errors that could be explicitly handled, check first: - /// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4 - - } else { - result = new RemoteOperationResult<>(move); - client.exhaustResponse(move.getResponseBodyAsStream()); - } - - Timber.i("Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage()); - } - - return result; - } - - protected boolean isSuccess(int status) { - return status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT; - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.kt new file mode 100644 index 00000000..5741766a --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/MoveRemoteFileOperation.kt @@ -0,0 +1,133 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2021 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod +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.isOneOf +import timber.log.Timber +import java.net.URL +import java.util.concurrent.TimeUnit + +/** + * Remote operation moving a remote file or folder in the ownCloud server to a different folder + * in the same account. + * + * Allows renaming the moving file/folder at the same time. + * + * @author David A. Velasco + * @author David González Verdugo + * @author Abel García de Prada + */ +open class MoveRemoteFileOperation( + private val sourceRemotePath: String, + private val targetRemotePath: String, +) : RemoteOperation() { + + /** + * Performs the rename operation. + * + * @param client Client object to communicate with the remote ownCloud server. + */ + override fun run(client: OwnCloudClient): RemoteOperationResult { + if (targetRemotePath == sourceRemotePath) { + // nothing to do! + return RemoteOperationResult(ResultCode.OK) + } + + if (targetRemotePath.startsWith(sourceRemotePath)) { + return RemoteOperationResult(ResultCode.INVALID_MOVE_INTO_DESCENDANT) + } + + /// perform remote operation + var result: RemoteOperationResult + try { + // After finishing a chunked upload, we have to move the resulting file from uploads folder to files one, + // so this uri has to be customizable + val srcWebDavUri = getSrcWebDavUriForClient(client) + val moveMethod = MoveMethod( + url = URL(srcWebDavUri.toString() + WebdavUtils.encodePath(sourceRemotePath)), + destinationUrl = client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(targetRemotePath), + ).apply { + addRequestHeaders(this) + setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS) + setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS) + } + + val status = client.executeHttpMethod(moveMethod) + + when { + isSuccess(status) -> { + result = RemoteOperationResult(ResultCode.OK) + } + isPreconditionFailed(status) -> { + result = RemoteOperationResult(ResultCode.INVALID_OVERWRITE) + client.exhaustResponse(moveMethod.getResponseBodyAsStream()) + + /// for other errors that could be explicitly handled, check first: + /// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4 + } + else -> { + result = RemoteOperationResult(moveMethod) + client.exhaustResponse(moveMethod.getResponseBodyAsStream()) + } + } + + Timber.i("Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}") + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}") + + } + return result + } + + /** + * For standard moves, we will use [OwnCloudClient.getUserFilesWebDavUri]. + * In case we need a different source Uri, override this method. + */ + open fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.userFilesWebDavUri + + /** + * For standard moves, we won't need any special headers. + * In case new headers are needed, override this method + */ + open fun addRequestHeaders(moveMethod: MoveMethod) { + } + + private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) + + private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED + + companion object { + private const val MOVE_READ_TIMEOUT = 10L + private const val MOVE_CONNECTION_TIMEOUT = 6L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.java deleted file mode 100644 index 24496ebe..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.java +++ /dev/null @@ -1,108 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.accounts.AccountUtils; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.DavUtils; -import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod; -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 timber.log.Timber; - -import java.net.URL; -import java.util.concurrent.TimeUnit; - -import static com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0; -import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK; - -/** - * Remote operation performing the read a file from the ownCloud server. - * - * @author David A. Velasco - * @author masensio - * @author David González Verdugo - */ - -public class ReadRemoteFileOperation extends RemoteOperation { - - private static final int SYNC_READ_TIMEOUT = 40000; - private static final int SYNC_CONNECTION_TIMEOUT = 5000; - - private String mRemotePath; - - /** - * Constructor - * - * @param remotePath Remote path of the file. - */ - public ReadRemoteFileOperation(String remotePath) { - mRemotePath = remotePath; - } - - /** - * Performs the read operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - 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( - new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)), - DEPTH_0, - DavUtils.getAllPropset()); - - propfind.setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS); - propfind.setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS); - final int status = client.executeHttpMethod(propfind); - - if (status == HttpConstants.HTTP_MULTI_STATUS - || status == HttpConstants.HTTP_OK) { - - final RemoteFile file = new RemoteFile(propfind.getRoot(), AccountUtils.getUserId(mAccount, mContext)); - - result = new RemoteOperationResult<>(OK); - result.setData(file); - - } else { - result = new RemoteOperationResult<>(propfind); - client.exhaustResponse(propfind.getResponseBodyAsStream()); - } - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Synchronizing file %s", mRemotePath); - } - - return result; - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.kt new file mode 100644 index 00000000..539ea92c --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFileOperation.kt @@ -0,0 +1,97 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2016 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK +import com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0 +import com.owncloud.android.lib.common.http.methods.webdav.DavUtils +import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod +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.isOneOf +import timber.log.Timber +import java.net.URL +import java.util.concurrent.TimeUnit + +/** + * Remote operation performing the read a file from the ownCloud server. + * + * @author David A. Velasco + * @author masensio + * @author David González Verdugo + */ + +class ReadRemoteFileOperation(val remotePath: String) : RemoteOperation() { + + /** + * Performs the read operation. + * + * @param client Client object to communicate with the remote ownCloud server. + */ + @Override + override fun run(client: OwnCloudClient): RemoteOperationResult { + try { + val propFind = PropfindMethod( + url = URL("${client.userFilesWebDavUri}${WebdavUtils.encodePath(remotePath)}"), + depth = DEPTH_0, + propertiesToRequest = DavUtils.allPropset + ).apply { + setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS) + setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS) + } + + val status = client.executeHttpMethod(propFind) + Timber.i("Read remote file $remotePath with status ${propFind.statusCode}") + + return if (isSuccess(status)) { + // TODO: Remove that !! + val remoteFile = RemoteFile.getRemoteFileFromDav( + propFind.root!!, + AccountUtils.getUserId(mAccount, mContext), mAccount.name + ) + + RemoteOperationResult(RemoteOperationResult.ResultCode.OK).apply { + data = remoteFile + } + } else { + RemoteOperationResult(propFind).also { + client.exhaustResponse(propFind.getResponseBodyAsStream()) + } + } + } catch (exception: Exception) { + return RemoteOperationResult(exception) + } + } + + private fun isSuccess(status: Int) = status.isOneOf(HTTP_MULTI_STATUS, HTTP_OK) + + companion object { + private const val SYNC_READ_TIMEOUT = 40_000L + private const val SYNC_CONNECTION_TIMEOUT = 5_000L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.java deleted file mode 100644 index bf6a0604..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.java +++ /dev/null @@ -1,128 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import at.bitfire.dav4jvm.PropertyRegistry; -import at.bitfire.dav4jvm.Response; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.accounts.AccountUtils; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.DavConstants; -import com.owncloud.android.lib.common.http.methods.webdav.DavUtils; -import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod; -import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes; -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 timber.log.Timber; - -import java.net.URL; -import java.util.ArrayList; - -import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK; - -/** - * Remote operation performing the read of remote file or folder in the ownCloud server. - * - * @author David A. Velasco - * @author masensio - * @author David González Verdugo - */ - -public class ReadRemoteFolderOperation extends RemoteOperation> { - - private String mRemotePath; - - /** - * Constructor - * - * @param remotePath Remote path of the file. - */ - public ReadRemoteFolderOperation(String remotePath) { - mRemotePath = remotePath; - } - - /** - * Performs the read operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult> run(OwnCloudClient client) { - RemoteOperationResult> result = null; - - try { - PropertyRegistry.INSTANCE.register(OCShareTypes.Factory.class.newInstance()); - PropfindMethod propfindMethod = new PropfindMethod( - new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)), - DavConstants.DEPTH_1, - DavUtils.getAllPropset()); - - int status = client.executeHttpMethod(propfindMethod); - - if (isSuccess(status)) { - ArrayList mFolderAndFiles = new ArrayList<>(); - - // parse data from remote folder - mFolderAndFiles.add( - new RemoteFile(propfindMethod.getRoot(), AccountUtils.getUserId(mAccount, mContext)) - ); - - // loop to update every child - for (Response resource : propfindMethod.getMembers()) { - RemoteFile file = new RemoteFile(resource, AccountUtils.getUserId(mAccount, mContext)); - mFolderAndFiles.add(file); - } - - // Result of the operation - result = new RemoteOperationResult<>(OK); - result.setData(mFolderAndFiles); - - } else { // synchronization failed - result = new RemoteOperationResult<>(propfindMethod); - } - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - } finally { - if (result == null) { - Timber.e("Synchronized " + mRemotePath + ": result is null"); - } else if (result.isSuccess()) { - Timber.i("Synchronized " + mRemotePath + ": " + result.getLogMessage()); - } else { - if (result.isException()) { - Timber.e(result.getException(), "Synchronized " + mRemotePath + ": " + result.getLogMessage()); - } else { - Timber.e("Synchronized " + mRemotePath + ": " + result.getLogMessage()); - } - } - } - return result; - } - - private boolean isSuccess(int status) { - return status == HttpConstants.HTTP_MULTI_STATUS || status == HttpConstants.HTTP_OK; - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.kt new file mode 100644 index 00000000..40747ace --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/ReadRemoteFolderOperation.kt @@ -0,0 +1,111 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2020 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import at.bitfire.dav4jvm.PropertyRegistry +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK +import com.owncloud.android.lib.common.http.methods.webdav.DavConstants +import com.owncloud.android.lib.common.http.methods.webdav.DavUtils +import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod +import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes +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.isOneOf +import timber.log.Timber +import java.net.URL + +/** + * Remote operation performing the read of remote file or folder in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + * @author David González Verdugo + */ +class ReadRemoteFolderOperation( + val remotePath: String +) : RemoteOperation>() { + + /** + * Performs the read operation. + * + * @param client Client object to communicate with the remote ownCloud server. + */ + override fun run(client: OwnCloudClient): RemoteOperationResult> { + try { + PropertyRegistry.register(OCShareTypes.Factory()) + + val propfindMethod = PropfindMethod( + URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)), + DavConstants.DEPTH_1, + DavUtils.allPropset + ) + + val status = client.executeHttpMethod(propfindMethod) + + if (isSuccess(status)) { + val mFolderAndFiles = ArrayList() + + // parse data from remote folder + // TODO: Remove that !! + val remoteFolder = RemoteFile.getRemoteFileFromDav( + davResource = propfindMethod.root!!, + userId = AccountUtils.getUserId(mAccount, mContext), + userName = mAccount.name + ) + mFolderAndFiles.add(remoteFolder) + + // loop to update every child + propfindMethod.members.forEach { resource -> + val remoteFile = RemoteFile.getRemoteFileFromDav( + davResource = resource, + userId = AccountUtils.getUserId(mAccount, mContext), + userName = mAccount.name + ) + mFolderAndFiles.add(remoteFile) + } + + // Result of the operation + return RemoteOperationResult>(ResultCode.OK).apply { + data = mFolderAndFiles + Timber.i("Synchronized $remotePath with ${mFolderAndFiles.size} files. ${this.logMessage}") + } + } else { // synchronization failed + return RemoteOperationResult>(propfindMethod).also { + Timber.w("Synchronized $remotePath ${it.logMessage}") + } + } + } catch (e: Exception) { + return RemoteOperationResult>(e).also { + Timber.e(it.exception, "Synchronized $remotePath") + } + } + } + + private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS) +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.java deleted file mode 100644 index 84a48bcd..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.java +++ /dev/null @@ -1,363 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import android.os.Parcel; -import android.os.Parcelable; - -import at.bitfire.dav4jvm.Property; -import at.bitfire.dav4jvm.Response; -import at.bitfire.dav4jvm.property.CreationDate; -import at.bitfire.dav4jvm.property.GetContentLength; -import at.bitfire.dav4jvm.property.GetContentType; -import at.bitfire.dav4jvm.property.GetETag; -import at.bitfire.dav4jvm.property.GetLastModified; -import at.bitfire.dav4jvm.property.OCId; -import at.bitfire.dav4jvm.property.OCPermissions; -import at.bitfire.dav4jvm.property.OCPrivatelink; -import at.bitfire.dav4jvm.property.OCSize; -import at.bitfire.dav4jvm.property.QuotaAvailableBytes; -import at.bitfire.dav4jvm.property.QuotaUsedBytes; -import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes; -import com.owncloud.android.lib.resources.shares.ShareType; -import timber.log.Timber; - -import java.io.File; -import java.io.Serializable; -import java.math.BigDecimal; -import java.util.LinkedList; -import java.util.List; - -/** - * Contains the data of a Remote File from a WebDavEntry - * - * @author masensio - * @author Christian Schabesberger - */ - -public class RemoteFile implements Parcelable, Serializable { - - /** - * Parcelable Methods - */ - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public RemoteFile createFromParcel(Parcel source) { - return new RemoteFile(source); - } - - @Override - public RemoteFile[] newArray(int size) { - return new RemoteFile[size]; - } - }; - /** - * Generated - should be refreshed every time the class changes!! - */ - private static final long serialVersionUID = -8965995357413958539L; - private String mRemotePath; - private String mMimeType; - private long mLength; - private long mCreationTimestamp; - private long mModifiedTimestamp; - private String mEtag; - private String mPermissions; - private String mRemoteId; - private long mSize; - private BigDecimal mQuotaUsedBytes; - private BigDecimal mQuotaAvailableBytes; - private String mPrivateLink; - private boolean mSharedByLink; - private boolean mSharedWithSharee; - - public RemoteFile() { - resetData(); - } - - /** - * Create new {@link RemoteFile} with given path. - *

- * The path received must be URL-decoded. Path separator must be File.separator, and it must be the first - * character in 'path'. - * - * @param path The remote path of the file. - */ - public RemoteFile(String path) { - resetData(); - if (path == null || path.length() <= 0 || !path.startsWith(File.separator)) { - throw new IllegalArgumentException("Trying to create a OCFile with a non valid remote path: " + path); - } - mRemotePath = path; - mCreationTimestamp = 0; - mLength = 0; - mMimeType = FileUtils.MIME_DIR; - mQuotaUsedBytes = BigDecimal.ZERO; - mQuotaAvailableBytes = BigDecimal.ZERO; - mPrivateLink = null; - } - - public RemoteFile(final Response davResource, String userId) { - this(RemoteFileUtil.Companion.getRemotePathFromUrl(davResource.getHref(), userId)); - final List properties = davResource.getProperties(); - - for (Property property : properties) { - if (property instanceof CreationDate) { - this.setCreationTimestamp( - Long.parseLong(((CreationDate) property).getCreationDate())); - } - if (property instanceof GetContentLength) { - this.setLength(((GetContentLength) property).getContentLength()); - } - if (property instanceof GetContentType) { - this.setMimeType(((GetContentType) property).getType()); - } - if (property instanceof GetLastModified) { - this.setModifiedTimestamp(((GetLastModified) property).getLastModified()); - } - if (property instanceof GetETag) { - this.setEtag(((GetETag) property).getETag()); - } - if (property instanceof OCPermissions) { - this.setPermissions(((OCPermissions) property).getPermission()); - } - if (property instanceof OCId) { - this.setRemoteId(((OCId) property).getId()); - } - if (property instanceof OCSize) { - this.setSize(((OCSize) property).getSize()); - } - if (property instanceof QuotaUsedBytes) { - this.setQuotaUsedBytes( - BigDecimal.valueOf(((QuotaUsedBytes) property).getQuotaUsedBytes())); - } - if (property instanceof QuotaAvailableBytes) { - this.setQuotaAvailableBytes( - BigDecimal.valueOf(((QuotaAvailableBytes) property).getQuotaAvailableBytes())); - } - if (property instanceof OCPrivatelink) { - this.setPrivateLink(((OCPrivatelink) property).getLink()); - } - if (property instanceof OCShareTypes) { - LinkedList list = ((OCShareTypes) property).getShareTypes(); - for (int i = 0; i < list.size(); i++) { - ShareType shareType = ShareType.Companion.fromValue(Integer.parseInt(list.get(i))); - if (shareType == null) { - Timber.d("Illegal share type value: " + list.get(i)); - continue; - } - if (shareType.equals(ShareType.PUBLIC_LINK)) { - this.setSharedViaLink(true); - } else if (shareType.equals(ShareType.USER) || - shareType.equals(ShareType.FEDERATED) || - shareType.equals(ShareType.GROUP)) { - this.setSharedWithSharee(true); - } - } - } - } - } - - /** - * Reconstruct from parcel - * - * @param source The source parcel - */ - protected RemoteFile(Parcel source) { - readFromParcel(source); - } - - /** - * Use this to find out if this file is a folder. - * - * @return true if it is a folder - */ - public boolean isFolder() { - return mMimeType != null && (mMimeType.equals(FileUtils.MIME_DIR) || mMimeType.equals(FileUtils.MIME_DIR_UNIX)); - } - - /** - * Getters and Setters - */ - - public String getRemotePath() { - return mRemotePath; - } - - public void setRemotePath(String remotePath) { - this.mRemotePath = remotePath; - } - - public String getMimeType() { - return mMimeType; - } - - public void setMimeType(String mimeType) { - this.mMimeType = mimeType; - } - - public long getLength() { - return mLength; - } - - public void setLength(long length) { - this.mLength = length; - } - - public long getCreationTimestamp() { - return mCreationTimestamp; - } - - public void setCreationTimestamp(long creationTimestamp) { - this.mCreationTimestamp = creationTimestamp; - } - - public long getModifiedTimestamp() { - return mModifiedTimestamp; - } - - public void setModifiedTimestamp(long modifiedTimestamp) { - this.mModifiedTimestamp = modifiedTimestamp; - } - - public String getEtag() { - return mEtag; - } - - public void setEtag(String etag) { - this.mEtag = etag; - } - - public String getPermissions() { - return mPermissions; - } - - public void setPermissions(String permissions) { - this.mPermissions = permissions; - } - - public String getRemoteId() { - return mRemoteId; - } - - public void setRemoteId(String remoteId) { - this.mRemoteId = remoteId; - } - - public long getSize() { - return mSize; - } - - public void setSize(long size) { - mSize = size; - } - - public void setQuotaUsedBytes(BigDecimal quotaUsedBytes) { - mQuotaUsedBytes = quotaUsedBytes; - } - - public void setQuotaAvailableBytes(BigDecimal quotaAvailableBytes) { - mQuotaAvailableBytes = quotaAvailableBytes; - } - - public String getPrivateLink() { - return mPrivateLink; - } - - public void setPrivateLink(String privateLink) { - mPrivateLink = privateLink; - } - - public void setSharedWithSharee(boolean shareWithSharee) { - mSharedWithSharee = shareWithSharee; - } - - public boolean isSharedWithSharee() { - return mSharedWithSharee; - } - - public void setSharedViaLink(boolean sharedViaLink) { - mSharedByLink = sharedViaLink; - } - - public boolean isSharedByLink() { - return mSharedByLink; - } - - /** - * Used internally. Reset all file properties - */ - private void resetData() { - mRemotePath = null; - mMimeType = null; - mLength = 0; - mCreationTimestamp = 0; - mModifiedTimestamp = 0; - mEtag = null; - mPermissions = null; - mRemoteId = null; - mSize = 0; - mQuotaUsedBytes = null; - mQuotaAvailableBytes = null; - mPrivateLink = null; - mSharedWithSharee = false; - mSharedByLink = false; - } - - public void readFromParcel(Parcel source) { - mRemotePath = source.readString(); - mMimeType = source.readString(); - mLength = source.readLong(); - mCreationTimestamp = source.readLong(); - mModifiedTimestamp = source.readLong(); - mEtag = source.readString(); - mPermissions = source.readString(); - mRemoteId = source.readString(); - mSize = source.readLong(); - mQuotaUsedBytes = (BigDecimal) source.readSerializable(); - mQuotaAvailableBytes = (BigDecimal) source.readSerializable(); - mPrivateLink = source.readString(); - } - - @Override - public int describeContents() { - return this.hashCode(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mRemotePath); - dest.writeString(mMimeType); - dest.writeLong(mLength); - dest.writeLong(mCreationTimestamp); - dest.writeLong(mModifiedTimestamp); - dest.writeString(mEtag); - dest.writeString(mPermissions); - dest.writeString(mRemoteId); - dest.writeLong(mSize); - dest.writeSerializable(mQuotaUsedBytes); - dest.writeSerializable(mQuotaAvailableBytes); - dest.writeString(mPrivateLink); - } -} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.kt new file mode 100644 index 00000000..87a6663e --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFile.kt @@ -0,0 +1,189 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2020 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.resources.files + +import android.net.Uri +import android.os.Parcelable +import at.bitfire.dav4jvm.PropStat +import at.bitfire.dav4jvm.Property +import at.bitfire.dav4jvm.Response +import at.bitfire.dav4jvm.property.CreationDate +import at.bitfire.dav4jvm.property.GetContentLength +import at.bitfire.dav4jvm.property.GetContentType +import at.bitfire.dav4jvm.property.GetETag +import at.bitfire.dav4jvm.property.GetLastModified +import at.bitfire.dav4jvm.property.OCId +import at.bitfire.dav4jvm.property.OCPermissions +import at.bitfire.dav4jvm.property.OCPrivatelink +import at.bitfire.dav4jvm.property.OCSize +import at.bitfire.dav4jvm.property.QuotaAvailableBytes +import at.bitfire.dav4jvm.property.QuotaUsedBytes +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes +import com.owncloud.android.lib.common.utils.isOneOf +import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.lib.resources.shares.ShareType.Companion.fromValue +import kotlinx.parcelize.Parcelize +import okhttp3.HttpUrl +import timber.log.Timber +import java.io.File +import java.math.BigDecimal + +/** + * Contains the data of a Remote File from a WebDavEntry + * + * The path received must be URL-decoded. Path separator must be File.separator, and it must be the first character in 'path'. + * + * @author masensio + * @author Christian Schabesberger + * @author Abel García de Prada + */ +@Parcelize +data class RemoteFile( + var remotePath: String, + var mimeType: String = "DIR", + var length: Long = 0, + var creationTimestamp: Long = 0, + var modifiedTimestamp: Long = 0, + var etag: String? = null, + var permissions: String? = null, + var remoteId: String? = null, + var size: Long = 0, + var quotaUsedBytes: BigDecimal? = null, + var quotaAvailableBytes: BigDecimal? = null, + var privateLink: String? = null, + var owner: String, + var sharedByLink: Boolean = false, + var sharedWithSharee: Boolean = false, +) : Parcelable { + + // TODO: Quotas not used. Use or remove them. + init { + require( + !(remotePath.isEmpty() || !remotePath.startsWith(File.separator)) + ) { "Trying to create a OCFile with a non valid remote path: $remotePath" } + } + + /** + * Use this to find out if this file is a folder. + * + * @return true if it is a folder + */ + val isFolder + get() = mimeType.isOneOf(MIME_DIR, MIME_DIR_UNIX) + + companion object { + + const val MIME_DIR = "DIR" + const val MIME_DIR_UNIX = "httpd/unix-directory" + + fun getRemoteFileFromDav(davResource: Response, userId: String, userName: String): RemoteFile { + val remotePath = getRemotePathFromUrl(davResource.href, userId) + val remoteFile = RemoteFile(remotePath = remotePath, owner = userName) + val properties = getPropertiesEvenIfPostProcessing(davResource) + + for (property in properties) { + when (property) { + is CreationDate -> { + remoteFile.creationTimestamp = property.creationDate.toLong() + } + is GetContentLength -> { + remoteFile.length = property.contentLength + } + is GetContentType -> { + property.type?.let { remoteFile.mimeType = it } + } + is GetLastModified -> { + remoteFile.modifiedTimestamp = property.lastModified + } + is GetETag -> { + remoteFile.etag = property.eTag + } + is OCPermissions -> { + remoteFile.permissions = property.permission + } + is OCId -> { + remoteFile.remoteId = property.id + } + is OCSize -> { + remoteFile.size = property.size + } + is QuotaUsedBytes -> { + remoteFile.quotaUsedBytes = BigDecimal.valueOf(property.quotaUsedBytes) + } + is QuotaAvailableBytes -> { + remoteFile.quotaAvailableBytes = BigDecimal.valueOf(property.quotaAvailableBytes) + } + is OCPrivatelink -> { + remoteFile.privateLink = property.link + } + is OCShareTypes -> { + val list = property.shareTypes + for (i in list.indices) { + val shareType = fromValue(list[i].toInt()) + if (shareType == null) { + Timber.d("Illegal share type value: " + list[i]) + continue + } + if (shareType == ShareType.PUBLIC_LINK) { + remoteFile.sharedByLink = true + } else if (shareType == ShareType.USER || shareType == ShareType.FEDERATED || shareType == ShareType.GROUP) { + remoteFile.sharedWithSharee = true + } + } + } + } + } + return remoteFile + } + + /** + * Retrieves a relative path from a remote file url + * + * + * Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt + * + * @param url remote file url + * @param userId file owner + * @return remote relative path of the file + */ + private fun getRemotePathFromUrl(url: HttpUrl, userId: String): String { + val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId + val absoluteDavPath = Uri.decode(url.encodedPath) + val pathToOc = absoluteDavPath.split(davFilesPath).first() + return absoluteDavPath.replace(pathToOc + davFilesPath, "") + } + + private fun getPropertiesEvenIfPostProcessing(response: Response): List { + return if (response.isSuccess()) + response.propstat.filter { propStat -> propStat.isSuccessOrPostProcessing() }.map { it.properties }.flatten() + else + emptyList() + } + + private fun PropStat.isSuccessOrPostProcessing() = (status.code / 100 == 2 || status.code == HttpConstants.HTTP_TOO_EARLY) + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.java deleted file mode 100644 index 4789d015..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.java +++ /dev/null @@ -1,96 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import android.net.Uri; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod; -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 timber.log.Timber; - -import java.net.URL; - -import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK; - -/** - * Remote operation performing the removal of a remote file or folder in the ownCloud server. - * - * @author David A. Velasco - * @author masensio - * @author David González Verdugo - */ -public class RemoveRemoteFileOperation extends RemoteOperation { - private String mRemotePath; - - protected boolean removeChunksFolder = false; - - /** - * Constructor - * - * @param remotePath RemotePath of the remote file or folder to remove from the server - */ - public RemoveRemoteFileOperation(String remotePath) { - mRemotePath = remotePath; - } - - /** - * Performs the rename operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; - - try { - Uri srcWebDavUri = removeChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri(); - - DeleteMethod deleteMethod = new DeleteMethod( - new URL(srcWebDavUri + WebdavUtils.encodePath(mRemotePath))); - - int status = client.executeHttpMethod(deleteMethod); - - result = isSuccess(status) ? - new RemoteOperationResult<>(OK) : - new RemoteOperationResult<>(deleteMethod); - - Timber.i("Remove " + mRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Remove " + mRemotePath + ": " + result.getLogMessage()); - } - - return result; - } - - private boolean isSuccess(int status) { - return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_NO_CONTENT; - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.kt new file mode 100644 index 00000000..a451256a --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoveRemoteFileOperation.kt @@ -0,0 +1,81 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2020 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.resources.files + +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_NO_CONTENT +import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK +import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod +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.isOneOf +import timber.log.Timber +import java.net.URL + +/** + * Remote operation performing the removal of a remote file or folder in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + * @author David González Verdugo + * @author Abel García de Prada + */ +open class RemoveRemoteFileOperation( + private val remotePath: String +) : RemoteOperation() { + + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val srcWebDavUri = getSrcWebDavUriForClient(client) + val deleteMethod = DeleteMethod( + URL(srcWebDavUri.toString() + WebdavUtils.encodePath(remotePath)) + ) + val status = client.executeHttpMethod(deleteMethod) + + result = if (isSuccess(status)) { + RemoteOperationResult(ResultCode.OK) + } else { + RemoteOperationResult(deleteMethod) + } + Timber.i("Remove $remotePath: ${result.logMessage}") + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Remove $remotePath: ${result.logMessage}") + } + return result + } + + /** + * For standard removals, we will use [OwnCloudClient.getUserFilesWebDavUri]. + * In case we need a different source Uri, override this method. + */ + open fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.userFilesWebDavUri + + private fun isSuccess(status: Int) = status.isOneOf(HTTP_OK, HTTP_NO_CONTENT) +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java deleted file mode 100644 index 8d53a612..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java +++ /dev/null @@ -1,131 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2019 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod; -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 timber.log.Timber; - -import java.io.File; -import java.net.URL; -import java.util.concurrent.TimeUnit; - -/** - * Remote operation performing the rename of a remote file or folder in the ownCloud server. - * - * @author David A. Velasco - * @author masensio - */ -public class RenameRemoteFileOperation extends RemoteOperation { - - private static final int RENAME_READ_TIMEOUT = 600000; - private static final int RENAME_CONNECTION_TIMEOUT = 5000; - - private String mOldName; - private String mOldRemotePath; - private String mNewName; - private String mNewRemotePath; - - /** - * Constructor - * - * @param oldName Old name of the file. - * @param oldRemotePath Old remote path of the file. - * @param newName New name to set as the name of file. - * @param isFolder 'true' for folder and 'false' for files - */ - public RenameRemoteFileOperation(String oldName, String oldRemotePath, String newName, - boolean isFolder) { - mOldName = oldName; - mOldRemotePath = oldRemotePath; - mNewName = newName; - - String parent = (new File(mOldRemotePath)).getParent(); - parent = (parent.endsWith(File.separator)) ? parent : parent + File.separator; - mNewRemotePath = parent + mNewName; - if (isFolder) { - mNewRemotePath += File.separator; - } - } - - /** - * Performs the rename operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - try { - if (mNewName.equals(mOldName)) { - return new RemoteOperationResult<>(ResultCode.OK); - } - - if (targetPathIsUsed(client)) { - return new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE); - } - - final MoveMethod move = new MoveMethod( - new URL(client.getUserFilesWebDavUri() + - WebdavUtils.encodePath(mOldRemotePath)), - client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mNewRemotePath), false); - - move.setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS); - move.setConnectionTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS); - - final int status = client.executeHttpMethod(move); - final RemoteOperationResult result = - (status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT) - ? new RemoteOperationResult<>(ResultCode.OK) - : new RemoteOperationResult<>(move); - - Timber.i("Rename " + mOldRemotePath + " to " + mNewRemotePath + ": " + result.getLogMessage()); - client.exhaustResponse(move.getResponseBodyAsStream()); - return result; - } catch (Exception e) { - final RemoteOperationResult result = new RemoteOperationResult<>(e); - Timber.e(e, - "Rename " + mOldRemotePath + " to " + ((mNewRemotePath == null) ? mNewName : mNewRemotePath) + ":" + - " " + result.getLogMessage()); - return result; - } - } - - /** - * Checks if a file with the new name already exists. - * - * @return 'True' if the target path is already used by an existing file. - */ - private boolean targetPathIsUsed(OwnCloudClient client) { - CheckPathExistenceRemoteOperation checkPathExistenceRemoteOperation = - new CheckPathExistenceRemoteOperation(mNewRemotePath, false); - RemoteOperationResult exists = checkPathExistenceRemoteOperation.execute(client); - return exists.isSuccess(); - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.kt new file mode 100644 index 00000000..8f669c69 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.kt @@ -0,0 +1,119 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2021 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod +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.isOneOf +import timber.log.Timber +import java.io.File +import java.net.URL +import java.util.concurrent.TimeUnit + +/** + * Remote operation performing the rename of a remote file or folder in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + */ +class RenameRemoteFileOperation( + private val oldName: String, + private val oldRemotePath: String, + private val newName: String, + isFolder: Boolean, +) : RemoteOperation() { + + private var newRemotePath: String + + init { + var parent = (File(oldRemotePath)).parent ?: throw IllegalArgumentException() + if (!parent.endsWith(File.separator)) { + parent = parent.plus(File.separator) + } + newRemotePath = parent.plus(newName) + if (isFolder) { + newRemotePath.plus(File.separator) + } + } + + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + if (newName == oldName) { + return RemoteOperationResult(ResultCode.OK) + } + + if (targetPathIsUsed(client)) { + return RemoteOperationResult(ResultCode.INVALID_OVERWRITE) + } + + val moveMethod: MoveMethod = MoveMethod( + url = URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(oldRemotePath)), + destinationUrl = client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(newRemotePath), + ).apply { + setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.MILLISECONDS) + setConnectionTimeout(RENAME_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) + } + val status = client.executeHttpMethod(moveMethod) + + result = if (isSuccess(status)) { + RemoteOperationResult(ResultCode.OK) + } else { + RemoteOperationResult(moveMethod) + } + + Timber.i("Rename $oldRemotePath to $newRemotePath: ${result.logMessage}") + client.exhaustResponse(moveMethod.getResponseBodyAsStream()) + return result + } catch (exception: Exception) { + result = RemoteOperationResult(exception) + Timber.e(exception, "Rename $oldRemotePath to $newName: ${result.logMessage}") + return result + } + } + + /** + * Checks if a file with the new name already exists. + * + * @return 'True' if the target path is already used by an existing file. + */ + private fun targetPathIsUsed(client: OwnCloudClient): Boolean { + val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation(newRemotePath, false) + val exists = checkPathExistenceRemoteOperation.execute(client) + return exists.isSuccess + } + + private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) + + companion object { + private const val RENAME_READ_TIMEOUT = 10_000L + private const val RENAME_CONNECTION_TIMEOUT = 5_000L + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromContentUriOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromContentUriOperation.kt index dfcef34d..a2f2cb26 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromContentUriOperation.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromContentUriOperation.kt @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2021 ownCloud GmbH. + * Copyright (C) 2022 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,22 +24,15 @@ package com.owncloud.android.lib.resources.files -import android.content.ContentResolver -import android.net.Uri -import android.provider.OpenableColumns import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.methods.webdav.PutMethod +import com.owncloud.android.lib.common.network.ContentUriRequestBody 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 okhttp3.MediaType -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody -import okio.BufferedSink -import okio.source +import com.owncloud.android.lib.common.utils.isOneOf import timber.log.Timber -import java.io.IOException import java.net.URL class UploadFileFromContentUriOperation( @@ -70,34 +63,6 @@ class UploadFileFromContentUriOperation( } fun isSuccess(status: Int): Boolean { - return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT - } -} - -class ContentUriRequestBody( - private val contentResolver: ContentResolver, - private val contentUri: Uri -) : RequestBody() { - - override fun contentType(): MediaType? { - val contentType = contentResolver.getType(contentUri) ?: return null - return contentType.toMediaTypeOrNull() - } - - override fun contentLength(): Long { - contentResolver.query(contentUri, null, null, null, null)?.use { cursor -> - val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) - cursor.moveToFirst() - return cursor.getLong(sizeIndex) - } ?: return -1 - } - - override fun writeTo(sink: BufferedSink) { - val inputStream = contentResolver.openInputStream(contentUri) - ?: throw IOException("Couldn't open content URI for reading: $contentUri") - - inputStream.source().use { source -> - sink.writeAll(source) - } + return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromFileSystemOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromFileSystemOperation.kt new file mode 100644 index 00000000..93a2357a --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadFileFromFileSystemOperation.kt @@ -0,0 +1,145 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.PutMethod +import com.owncloud.android.lib.common.network.FileRequestBody +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener +import com.owncloud.android.lib.common.network.WebdavUtils +import com.owncloud.android.lib.common.operations.OperationCancelledException +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.isOneOf +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import timber.log.Timber +import java.io.File +import java.net.URL +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Remote operation performing the upload of a remote file to the ownCloud server. + * + * @author David A. Velasco + * @author masensio + * @author David González Verdugo + * @author Abel García de Prada + */ +open class UploadFileFromFileSystemOperation( + val localPath: String, + val remotePath: String, + val mimeType: String, + val lastModifiedTimestamp: String, + val requiredEtag: String?, +) : RemoteOperation() { + + protected val cancellationRequested = AtomicBoolean(false) + protected var putMethod: PutMethod? = null + protected val dataTransferListener: MutableSet = HashSet() + protected var fileRequestBody: FileRequestBody? = null + + var etag: String = "" + + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + if (cancellationRequested.get()) { + // the operation was cancelled before getting it's turn to be executed in the queue of uploads + result = RemoteOperationResult(OperationCancelledException()) + Timber.i("Upload of $localPath to $remotePath has been cancelled") + } else { + // perform the upload + result = uploadFile(client) + Timber.i("Upload of $localPath to $remotePath: ${result.logMessage}") + } + } catch (e: Exception) { + if (putMethod?.isAborted == true) { + result = RemoteOperationResult(OperationCancelledException()) + Timber.e(result.exception, "Upload of $localPath to $remotePath has been aborted with this message: ${result.logMessage}") + } else { + result = RemoteOperationResult(e) + Timber.e(result.exception, "Upload of $localPath to $remotePath has failed with this message: ${result.logMessage}") + } + } + return result + } + + @Throws(Exception::class) + protected open fun uploadFile(client: OwnCloudClient): RemoteOperationResult { + val fileToUpload = File(localPath) + val mediaType: MediaType? = mimeType.toMediaTypeOrNull() + + fileRequestBody = FileRequestBody(fileToUpload, mediaType).also { + synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) } + } + + putMethod = PutMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)), fileRequestBody!!).apply { + retryOnConnectionFailure = false + if (!requiredEtag.isNullOrBlank()) { + addRequestHeader(HttpConstants.IF_MATCH_HEADER, requiredEtag) + } + addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileToUpload.length().toString()) + addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, lastModifiedTimestamp) + } + + val status = client.executeHttpMethod(putMethod) + return if (isSuccess(status)) { + etag = WebdavUtils.getEtagFromResponse(putMethod) + // Get rid of extra quotas + etag = etag.replace("\"", "") + if (etag.isEmpty()) { + Timber.e("Could not read eTag from response uploading %s", localPath) + } else { + Timber.d("File uploaded successfully. New etag for file ${fileToUpload.name} is $etag") + } + RemoteOperationResult(ResultCode.OK).apply { data = Unit } + } else { // synchronization failed + RemoteOperationResult(putMethod) + } + } + + fun addDataTransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListener) { dataTransferListener.add(listener) } + fileRequestBody?.addDatatransferProgressListener(listener) + } + + fun removeDataTransferProgressListener(listener: OnDatatransferProgressListener) { + synchronized(dataTransferListener) { dataTransferListener.remove(listener) } + fileRequestBody?.removeDatatransferProgressListener(listener) + } + + fun cancel() { + synchronized(cancellationRequested) { + cancellationRequested.set(true) + putMethod?.abort() + } + } + + fun isSuccess(status: Int): Boolean { + return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadRemoteFileOperation.java deleted file mode 100644 index 43181b28..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/UploadRemoteFileOperation.java +++ /dev/null @@ -1,181 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.http.methods.webdav.PutMethod; -import com.owncloud.android.lib.common.network.FileRequestBody; -import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; -import com.owncloud.android.lib.common.network.WebdavUtils; -import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import okhttp3.MediaType; -import timber.log.Timber; - -import java.io.File; -import java.net.URL; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK; - -/** - * Remote operation performing the upload of a remote file to the ownCloud server. - * - * @author David A. Velasco - * @author masensio - * @author David González Verdugo - */ - -public class UploadRemoteFileOperation extends RemoteOperation { - - protected final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - protected String mLocalPath; - protected String mRemotePath; - protected String mMimeType; - protected String mFileLastModifTimestamp; - protected PutMethod mPutMethod = null; - protected String mRequiredEtag = null; - protected Set mDataTransferListeners = new HashSet<>(); - - protected FileRequestBody mFileRequestBody = null; - - public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType, - String fileLastModifTimestamp) { - mLocalPath = localPath; - mRemotePath = remotePath; - mMimeType = mimeType; - mFileLastModifTimestamp = fileLastModifTimestamp; - } - - public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType, - String requiredEtag, String fileLastModifTimestamp) { - this(localPath, remotePath, mimeType, fileLastModifTimestamp); - mRequiredEtag = requiredEtag; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; - - try { - - if (mCancellationRequested.get()) { - // the operation was cancelled before getting it's turn to be executed in the queue of uploads - result = new RemoteOperationResult<>(new OperationCancelledException()); - } else { - // perform the upload - result = uploadFile(client); - Timber.i("Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage()); - } - - } catch (Exception e) { - - if (mPutMethod != null && mPutMethod.isAborted()) { - result = new RemoteOperationResult<>(new OperationCancelledException()); - Timber.e(result.getException(), - "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage()); - } else { - result = new RemoteOperationResult<>(e); - Timber.e(e, "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage()); - } - } - - return result; - } - - protected RemoteOperationResult uploadFile(OwnCloudClient client) throws Exception { - - File fileToUpload = new File(mLocalPath); - - MediaType mediaType = MediaType.parse(mMimeType); - - mFileRequestBody = new FileRequestBody(fileToUpload, mediaType); - - synchronized (mDataTransferListeners) { - mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners); - } - - mPutMethod = new PutMethod( - new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)), mFileRequestBody); - - mPutMethod.setRetryOnConnectionFailure(false); - - if (mRequiredEtag != null && mRequiredEtag.length() > 0) { - mPutMethod.addRequestHeader(HttpConstants.IF_MATCH_HEADER, mRequiredEtag); - } - - mPutMethod.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(fileToUpload.length())); - mPutMethod.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp); - - int status = client.executeHttpMethod(mPutMethod); - - if (isSuccess(status)) { - return new RemoteOperationResult<>(OK); - - } else { // synchronization failed - return new RemoteOperationResult<>(mPutMethod); - } - } - - public Set getDataTransferListeners() { - return mDataTransferListeners; - } - - public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - if (mFileRequestBody != null) { - mFileRequestBody.addDatatransferProgressListener(listener); - } - } - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - if (mFileRequestBody != null) { - mFileRequestBody.removeDatatransferProgressListener(listener); - } - } - - public void cancel() { - synchronized (mCancellationRequested) { - mCancellationRequested.set(true); - if (mPutMethod != null) { - mPutMethod.abort(); - } - } - } - - public boolean isSuccess(int status) { - return ((status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED || - status == HttpConstants.HTTP_NO_CONTENT)); - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadFromFileSystemOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadFromFileSystemOperation.kt new file mode 100644 index 00000000..01a6b086 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadFromFileSystemOperation.kt @@ -0,0 +1,122 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.owncloud.android.lib.resources.files.chunks + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.methods.webdav.PutMethod +import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody +import com.owncloud.android.lib.common.operations.OperationCancelledException +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import com.owncloud.android.lib.resources.files.FileUtils.MODE_READ_ONLY +import com.owncloud.android.lib.resources.files.UploadFileFromFileSystemOperation +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import timber.log.Timber +import java.io.File +import java.io.RandomAccessFile +import java.net.URL +import java.nio.channels.FileChannel +import java.util.concurrent.TimeUnit +import kotlin.math.ceil + +/** + * Remote operation performing the chunked upload of a remote file to the ownCloud server. + * + * @author David A. Velasco + * @author David González Verdugo + * @author Abel García de Prada + */ +class ChunkedUploadFromFileSystemOperation( + private val transferId: String, + localPath: String, + remotePath: String, + mimeType: String, + lastModifiedTimestamp: String, + requiredEtag: String?, +) : UploadFileFromFileSystemOperation( + localPath = localPath, + remotePath = remotePath, + mimeType = mimeType, + lastModifiedTimestamp = lastModifiedTimestamp, + requiredEtag = requiredEtag +) { + + @Throws(Exception::class) + override fun uploadFile(client: OwnCloudClient): RemoteOperationResult { + lateinit var result: RemoteOperationResult + + val fileToUpload = File(localPath) + val mediaType: MediaType? = mimeType.toMediaTypeOrNull() + val raf = RandomAccessFile(fileToUpload, MODE_READ_ONLY) + val channel: FileChannel = raf.channel + + val fileRequestBody = ChunkFromFileRequestBody(fileToUpload, mediaType, channel).also { + synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) } + } + + val uriPrefix = client.uploadsWebDavUri.toString() + File.separator + transferId + val totalLength = fileToUpload.length() + val chunkCount = ceil(totalLength.toDouble() / CHUNK_SIZE).toLong() + var offset: Long = 0 + + for (chunkIndex in 0..chunkCount) { + fileRequestBody.setOffset(offset) + + if (cancellationRequested.get()) { + result = RemoteOperationResult(OperationCancelledException()) + break + } else { + putMethod = PutMethod(URL(uriPrefix + File.separator + chunkIndex), fileRequestBody).apply { + if (chunkIndex == chunkCount - 1) { + // Added a high timeout to the last chunk due to when the last chunk + // arrives to the server with the last PUT, all chunks get assembled + // within that PHP request, so last one takes longer. + setReadTimeout(LAST_CHUNK_TIMEOUT.toLong(), TimeUnit.MILLISECONDS) + } + } + + val status = client.executeHttpMethod(putMethod) + + Timber.d("Upload of $localPath to $remotePath, chunk index $chunkIndex, count $chunkCount, HTTP result status $status") + + if (isSuccess(status)) { + result = RemoteOperationResult(ResultCode.OK) + } else { + result = RemoteOperationResult(putMethod) + break + } + } + offset += CHUNK_SIZE + } + channel.close() + raf.close() + return result + } + + companion object { + const val CHUNK_SIZE = 10_240_000L // 10 MB + private const val LAST_CHUNK_TIMEOUT = 900_000 // 15 mins. + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadRemoteFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadRemoteFileOperation.java deleted file mode 100644 index 10b8ea36..00000000 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/ChunkedUploadRemoteFileOperation.java +++ /dev/null @@ -1,130 +0,0 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -package com.owncloud.android.lib.resources.files.chunks; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.methods.webdav.PutMethod; -import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody; -import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; -import okhttp3.MediaType; -import timber.log.Timber; - -import java.io.File; -import java.io.RandomAccessFile; -import java.net.URL; -import java.nio.channels.FileChannel; -import java.util.concurrent.TimeUnit; - -import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK; - -/** - * Remote operation performing the chunked upload of a remote file to the ownCloud server. - * - * @author David A. Velasco - * @author David González Verdugo - */ -public class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation { - - public static final long CHUNK_SIZE = 1024000; - private static final int LAST_CHUNK_TIMEOUT = 900000; //15 mins. - - private String mTransferId; - - public ChunkedUploadRemoteFileOperation(String transferId, String localPath, String remotePath, String mimeType, - String requiredEtag, String fileLastModifTimestamp) { - super(localPath, remotePath, mimeType, requiredEtag, fileLastModifTimestamp); - mTransferId = transferId; - } - - @Override - protected RemoteOperationResult uploadFile(OwnCloudClient client) throws Exception { - int status; - RemoteOperationResult result = null; - FileChannel channel; - RandomAccessFile raf; - - File fileToUpload = new File(mLocalPath); - MediaType mediaType = MediaType.parse(mMimeType); - - raf = new RandomAccessFile(fileToUpload, "r"); - channel = raf.getChannel(); - - mFileRequestBody = new ChunkFromFileRequestBody(fileToUpload, mediaType, channel, CHUNK_SIZE); - - synchronized (mDataTransferListeners) { - mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners); - } - - long offset = 0; - String uriPrefix = client.getUploadsWebDavUri() + File.separator + mTransferId; - long totalLength = fileToUpload.length(); - long chunkCount = (long) Math.ceil((double) totalLength / CHUNK_SIZE); - - for (int chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++, offset += CHUNK_SIZE) { - - ((ChunkFromFileRequestBody) mFileRequestBody).setOffset(offset); - - if (mCancellationRequested.get()) { - result = new RemoteOperationResult<>(new OperationCancelledException()); - break; - } else { - mPutMethod = new PutMethod( - new URL(uriPrefix + File.separator + chunkIndex), mFileRequestBody); - - if (chunkIndex == chunkCount - 1) { - // Added a high timeout to the last chunk due to when the last chunk - // arrives to the server with the last PUT, all chunks get assembled - // within that PHP request, so last one takes longer. - mPutMethod.setReadTimeout(LAST_CHUNK_TIMEOUT, TimeUnit.MILLISECONDS); - } - - status = client.executeHttpMethod(mPutMethod); - - Timber.d("Upload of " + mLocalPath + " to " + mRemotePath + - ", chunk index " + chunkIndex + ", count " + chunkCount + - ", HTTP result status " + status); - - if (isSuccess(status)) { - result = new RemoteOperationResult<>(OK); - } else { - result = new RemoteOperationResult<>(mPutMethod); - break; - } - } - } - - if (channel != null) { - channel.close(); - } - - if (raf != null) { - raf.close(); - } - - return result; - } -} \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.kt similarity index 56% rename from owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.java rename to owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.kt index 767a20b8..9bdccd17 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/MoveRemoteChunksFileOperation.kt @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. + * Copyright (C) 2021 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,30 +21,38 @@ * THE SOFTWARE. * */ +package com.owncloud.android.lib.resources.files.chunks -package com.owncloud.android.lib.resources.files.chunks; - -import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation; +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod +import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation /** * Remote operation to move the file built from chunks after uploading it * * @author David González Verdugo + * @author Abel García de Prada */ -public class MoveRemoteChunksFileOperation extends MoveRemoteFileOperation { +class MoveRemoteChunksFileOperation( + sourceRemotePath: String, + targetRemotePath: String, + private val fileLastModificationTimestamp: String, + private val fileLength: Long +) : MoveRemoteFileOperation( + sourceRemotePath = sourceRemotePath, + targetRemotePath = targetRemotePath, +) { - /** - * Constructor. - * - * @param srcRemotePath Remote path of the file/folder to move. - * @param targetRemotePath Remove path desired for the file/folder after moving it. - * @param overwrite - */ - public MoveRemoteChunksFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite, - String fileLastModifTimestamp, long fileLength) { - super(srcRemotePath, targetRemotePath, overwrite); - moveChunkedFile = true; - mFileLastModifTimestamp = fileLastModifTimestamp; - mFileLength = fileLength; + override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri + + override fun addRequestHeaders(moveMethod: MoveMethod) { + super.addRequestHeaders(moveMethod) + + moveMethod.apply { + addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, fileLastModificationTimestamp) + addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileLength.toString()) + } } -} \ No newline at end of file +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.kt similarity index 75% rename from owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.java rename to owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.kt index 16d2b8bd..150a4dd1 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/chunks/RemoveRemoteChunksFolderOperation.kt @@ -22,20 +22,12 @@ * THE SOFTWARE. * */ +package com.owncloud.android.lib.resources.files.chunks -package com.owncloud.android.lib.resources.files.chunks; +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation -import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation; - -public class RemoveRemoteChunksFolderOperation extends RemoveRemoteFileOperation { - - /** - * Constructor - * - * @param remotePath RemotePath of the remote file or folder to remove from the server - */ - public RemoveRemoteChunksFolderOperation(String remotePath) { - super(remotePath); - removeChunksFolder = true; - } -} \ No newline at end of file +class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) { + override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFileUtil.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/ChunkService.kt similarity index 55% rename from owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFileUtil.kt rename to owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/ChunkService.kt index 30550e70..10410b9e 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/RemoteFileUtil.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/ChunkService.kt @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. + * Copyright (C) 2021 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,29 +21,21 @@ * THE SOFTWARE. * */ -package com.owncloud.android.lib.resources.files -import android.net.Uri -import com.owncloud.android.lib.common.OwnCloudClient -import okhttp3.HttpUrl +package com.owncloud.android.lib.resources.files.services -class RemoteFileUtil { - companion object { - /** - * Retrieves a relative path from a remote file url - * - * - * Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt - * - * @param url remote file url - * @param userId file owner - * @return remote relative path of the file - */ - fun getRemotePathFromUrl(url: HttpUrl, userId: String): String? { - val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId - val absoluteDavPath = Uri.decode(url.encodedPath) - val pathToOc = absoluteDavPath.split(davFilesPath)[0] - return absoluteDavPath.replace(pathToOc + davFilesPath, "") - } - } +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.Service + +interface ChunkService : Service { + fun removeFile( + remotePath: String + ): RemoteOperationResult + + fun moveFile( + sourceRemotePath: String, + targetRemotePath: String, + fileLastModificationTimestamp: String, + fileLength: Long + ): RemoteOperationResult } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/FileService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/FileService.kt index 4de85052..51a85c46 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/FileService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/FileService.kt @@ -25,8 +25,53 @@ package com.owncloud.android.lib.resources.files.services import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.Service +import com.owncloud.android.lib.resources.files.RemoteFile interface FileService : Service { - fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult + + fun checkPathExistence( + path: String, + isUserLogged: Boolean + ): RemoteOperationResult + + fun copyFile( + sourceRemotePath: String, + targetRemotePath: String, + ): RemoteOperationResult + + fun createFolder( + remotePath: String, + createFullPath: Boolean, + isChunkFolder: Boolean = false + ): RemoteOperationResult + + fun downloadFile( + remotePath: String, + localTempPath: String + ): RemoteOperationResult + + fun moveFile( + sourceRemotePath: String, + targetRemotePath: String, + ): RemoteOperationResult + + fun readFile( + remotePath: String + ): RemoteOperationResult + + fun refreshFolder( + remotePath: String + ): RemoteOperationResult> + + fun removeFile( + remotePath: String + ): RemoteOperationResult + + fun renameFile( + oldName: String, + oldRemotePath: String, + newName: String, + isFolder: Boolean, + ): RemoteOperationResult } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCChunkService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCChunkService.kt new file mode 100644 index 00000000..cf5d4820 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCChunkService.kt @@ -0,0 +1,50 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2021 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.resources.files.services.implementation + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.files.chunks.MoveRemoteChunksFileOperation +import com.owncloud.android.lib.resources.files.chunks.RemoveRemoteChunksFolderOperation +import com.owncloud.android.lib.resources.files.services.ChunkService + +class OCChunkService(override val client: OwnCloudClient) : ChunkService { + + override fun removeFile(remotePath: String): RemoteOperationResult = + RemoveRemoteChunksFolderOperation(remotePath = remotePath).execute(client) + + override fun moveFile( + sourceRemotePath: String, + targetRemotePath: String, + fileLastModificationTimestamp: String, + fileLength: Long + ): RemoteOperationResult = + MoveRemoteChunksFileOperation( + sourceRemotePath = sourceRemotePath, + targetRemotePath = targetRemotePath, + fileLastModificationTimestamp = fileLastModificationTimestamp, + fileLength = fileLength, + ).execute(client) +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCFileService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCFileService.kt index b0670a9f..32ac22ca 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCFileService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/files/services/implementation/OCFileService.kt @@ -26,12 +26,24 @@ package com.owncloud.android.lib.resources.files.services.implementation import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation +import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation +import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation +import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation import com.owncloud.android.lib.resources.files.GetUrlToOpenInWebRemoteOperation +import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation +import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation +import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation +import com.owncloud.android.lib.resources.files.RemoteFile +import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation +import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation import com.owncloud.android.lib.resources.files.services.FileService -class OCFileService(override val client: OwnCloudClient) : - FileService { - override fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult = +class OCFileService(override val client: OwnCloudClient) : FileService { + + override fun checkPathExistence( + path: String, + isUserLogged: Boolean + ): RemoteOperationResult = CheckPathExistenceRemoteOperation( remotePath = path, isUserLoggedIn = isUserLogged @@ -40,4 +52,75 @@ class OCFileService(override val client: OwnCloudClient) : override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult = GetUrlToOpenInWebRemoteOperation(openWithWebEndpoint = openWebEndpoint, fileId = fileId).execute(client) + override fun copyFile( + sourceRemotePath: String, + targetRemotePath: String + ): RemoteOperationResult = + CopyRemoteFileOperation( + srcRemotePath = sourceRemotePath, + targetRemotePath = targetRemotePath + ).execute(client) + + override fun createFolder( + remotePath: String, + createFullPath: Boolean, + isChunkFolder: Boolean + ): RemoteOperationResult = + CreateRemoteFolderOperation( + remotePath = remotePath, + createFullPath = createFullPath, + isChunksFolder = isChunkFolder + ).execute(client) + + override fun downloadFile( + remotePath: String, + localTempPath: String + ): RemoteOperationResult = + DownloadRemoteFileOperation( + remotePath = remotePath, + localFolderPath = localTempPath + ).execute(client) + + override fun moveFile( + sourceRemotePath: String, + targetRemotePath: String, + ): RemoteOperationResult = + MoveRemoteFileOperation( + sourceRemotePath = sourceRemotePath, + targetRemotePath = targetRemotePath, + ).execute(client) + + override fun readFile( + remotePath: String + ): RemoteOperationResult = + ReadRemoteFileOperation( + remotePath = remotePath + ).execute(client) + + override fun refreshFolder( + remotePath: String + ): RemoteOperationResult> = + ReadRemoteFolderOperation( + remotePath = remotePath + ).execute(client) + + override fun removeFile( + remotePath: String + ): RemoteOperationResult = + RemoveRemoteFileOperation( + remotePath = remotePath + ).execute(client) + + override fun renameFile( + oldName: String, + oldRemotePath: String, + newName: String, + isFolder: Boolean + ): RemoteOperationResult = + RenameRemoteFileOperation( + oldName = oldName, + oldRemotePath = oldRemotePath, + newName = newName, + isFolder = isFolder + ).execute(client) } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteShareesOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteShareesOperation.kt index 10154820..a03cac72 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteShareesOperation.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteShareesOperation.kt @@ -99,11 +99,11 @@ class GetRemoteShareesOperation .appendQueryParameter(PARAM_PER_PAGE, perPage.toString()) .build() - private fun parseResponse(response: String): ShareeOcsResponse? { + private fun parseResponse(response: String?): ShareeOcsResponse? { val moshi = Moshi.Builder().build() val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java) val adapter: JsonAdapter> = moshi.adapter(type) - return adapter.fromJson(response)!!.ocs.data + return response?.let { adapter.fromJson(it)?.ocs?.data } } private fun onResultUnsuccessful( @@ -123,7 +123,7 @@ class GetRemoteShareesOperation private fun onRequestSuccessful(response: String?): RemoteOperationResult { val result = RemoteOperationResult(OK) Timber.d("Successful response: $response") - result.data = parseResponse(response!!) + result.data = parseResponse(response) Timber.d("*** Get Users or groups completed ") return result } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt new file mode 100644 index 00000000..42ee4b12 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt @@ -0,0 +1,108 @@ +/* ownCloud Android Library is available under MIT license + * + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.owncloud.android.lib.resources.webfinger + +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod +import com.owncloud.android.lib.common.http.methods.nonwebdav.HttpMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.webfinger.responses.WebfingerJrdResponse +import com.squareup.moshi.Moshi +import timber.log.Timber +import java.net.URL + +class GetOCInstanceViaWebfingerOperation( + private val lockupServerDomain: String, + private val rel: String, + private val resource: String, +) : RemoteOperation() { + + private fun buildRequestUri() = + Uri.parse(lockupServerDomain).buildUpon() + .path(ENDPOINT_WEBFINGER_PATH) + .appendQueryParameter("rel", rel) + .appendQueryParameter("resource", resource) + .build() + + private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK + + private fun parseResponse(response: String): WebfingerJrdResponse { + val moshi = Moshi.Builder().build() + val adapter = moshi.adapter(WebfingerJrdResponse::class.java) + return adapter.fromJson(response)!! + } + + private fun onResultUnsuccessful( + method: HttpMethod, + response: String?, + status: Int + ): RemoteOperationResult { + Timber.e("Failed requesting webfinger info") + if (response != null) { + Timber.e("*** status code: $status; response message: $response") + } else { + Timber.e("*** status code: $status") + } + return RemoteOperationResult(method) + } + + private fun onRequestSuccessful(rawResponse: String): RemoteOperationResult { + val response = parseResponse(rawResponse) + for (i in response.links) { + if (i.rel == rel) { + val operationResult = RemoteOperationResult(RemoteOperationResult.ResultCode.OK) + operationResult.data = i.href + return operationResult + } + } + Timber.e("Could not find ownCloud relevant information in webfinger response: $rawResponse") + throw java.lang.Exception("Could not find ownCloud relevant information in webfinger response") + } + + override fun run(client: OwnCloudClient): RemoteOperationResult { + val requestUri = buildRequestUri() + val getMethod = GetMethod(URL(requestUri.toString())) + return try { + val status = client.executeHttpMethod(getMethod) + val response = getMethod.getResponseBodyAsString()!! + + if (isSuccess(status)) { + onRequestSuccessful(response) + } else { + onResultUnsuccessful(getMethod, response, status) + } + } catch (e: Exception) { + Timber.e(e, "Requesting webfinger info failed") + RemoteOperationResult(e) + } + } + + companion object { + private const val ENDPOINT_WEBFINGER_PATH = "/.well-known/webfinger" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt new file mode 100644 index 00000000..45e85a75 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt @@ -0,0 +1,39 @@ +/* ownCloud Android Library is available under MIT license + * + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.owncloud.android.lib.resources.webfinger.responses + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class WebfingerJrdResponse( + val subject: String, + val links: List +) + +@JsonClass(generateAdapter = true) +data class LinkItem( + val href: String, + val rel: String +) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt new file mode 100644 index 00000000..4e057beb --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt @@ -0,0 +1,29 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.lib.resources.webfinger.services + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult + +interface WebfingerService { + fun getInstanceFromWebfinger( + lookupServer: String, + username: String, + client: OwnCloudClient, + ): RemoteOperationResult +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt new file mode 100644 index 00000000..3eab83d4 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt @@ -0,0 +1,38 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.lib.resources.webfinger.services.implementation + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.webfinger.GetOCInstanceViaWebfingerOperation +import com.owncloud.android.lib.resources.webfinger.services.WebfingerService + +class OCWebfingerService : WebfingerService { + + override fun getInstanceFromWebfinger( + lookupServer: String, + username: String, + client: OwnCloudClient, + ): RemoteOperationResult = + GetOCInstanceViaWebfingerOperation(lookupServer, OWNCLOUD_REL, username).execute(client) + + companion object { + private const val OWNCLOUD_REL = "http://webfinger.owncloud/rel/server-instance" + } + +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt index 3981ab5b..11c51764 100644 --- a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt @@ -63,31 +63,31 @@ class ShareeResponseTest { @Test fun `example response - ok - correct sturcture`() { val response = loadResponses(EXAMPLE_RESPONSE_JSON)!! - assertEquals(2, response.ocs.data.groups.size) - assertEquals(0, response.ocs.data.remotes.size) - assertEquals(2, response.ocs.data.users.size) - assertEquals(0, response.ocs.data.exact?.groups?.size) - assertEquals(0, response.ocs.data.exact?.remotes?.size) - assertEquals(1, response.ocs.data.exact?.users?.size) - assertEquals("user1@user1.com", response.ocs.data.users.get(0).value.additionalInfo) - assertNull(response.ocs.data.users[1].value.additionalInfo) + assertEquals(2, response.ocs.data?.groups?.size) + assertEquals(0, response.ocs.data?.remotes?.size) + assertEquals(2, response.ocs.data?.users?.size) + assertEquals(0, response.ocs.data?.exact?.groups?.size) + assertEquals(0, response.ocs.data?.exact?.remotes?.size) + assertEquals(1, response.ocs.data?.exact?.users?.size) + assertEquals("user1@user1.com", response.ocs.data?.users?.get(0)?.value?.additionalInfo) + assertNull(response.ocs.data?.users?.get(1)?.value?.additionalInfo) } @Test fun `check empty response - ok - parsing ok`() { val response = loadResponses(EMPTY_RESPONSE_JSON)!! - assertTrue(response.ocs.data.exact?.groups?.isEmpty()!!) - assertTrue(response.ocs.data.exact?.remotes?.isEmpty()!!) - assertTrue(response.ocs.data.exact?.users?.isEmpty()!!) - assertTrue(response.ocs.data.groups.isEmpty()) - assertTrue(response.ocs.data.remotes.isEmpty()) - assertTrue(response.ocs.data.users.isEmpty()) + assertTrue(response.ocs.data?.exact?.groups?.isEmpty()!!) + assertTrue(response.ocs.data?.exact?.remotes?.isEmpty()!!) + assertTrue(response.ocs.data?.exact?.users?.isEmpty()!!) + assertTrue(response.ocs.data?.groups?.isEmpty()!!) + assertTrue(response.ocs.data?.remotes?.isEmpty()!!) + assertTrue(response.ocs.data?.users?.isEmpty()!!) } companion object { - val RESOURCES_PATH = + private const val RESOURCES_PATH = "src/test/responses/com.owncloud.android.lib.resources.sharees.responses" - val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" - val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" + const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" + const val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" } -} \ No newline at end of file +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt new file mode 100644 index 00000000..2d0c6235 --- /dev/null +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt @@ -0,0 +1,49 @@ +package com.owncloud.android.lib.resources.webfinger.responses + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Moshi +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.io.File + +class WebfingerResponseTest { + lateinit var adapter: JsonAdapter + + private fun loadResponses(fileName: String) = adapter.fromJson(File(fileName).readText()) + + @Before + fun prepare() { + val moshi = Moshi.Builder().build() + adapter = moshi.adapter(WebfingerJrdResponse::class.java) + } + + @Test + fun `check rel in too much information - ok`() { + val response = loadResponses(TOO_MUCH_INFORMATION_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + Assert.assertEquals("http://webfinger.owncloud/rel/server-instance", response.links[0].rel) + } + + @Test(expected = JsonDataException::class) + fun `check key value pairs - ko - no href key`() { + val response = loadResponses(BROKEN_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + } + + @Test(expected = JsonDataException::class) + fun `check key value pairs - ko - no rel key`() { + val response = loadResponses(BROKEN_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + } + + companion object { + private const val RESOURCES_PATH = + "src/test/responses/com.owncloud.android.lib.resources.webfinger.responses" + private const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/simple_response.json" + private const val TOO_MUCH_INFORMATION_JSON = "$RESOURCES_PATH/to_much_information_response.json" + private const val BROKEN_JSON = "$RESOURCES_PATH/broken_response.json" + private const val NOT_CONTAINING_RELEVANT_INFORMATION_JSON = "$RESOURCES_PATH/not_containing_relevant_info_response.json" + } +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json new file mode 100644 index 00000000..85dd2279 --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json @@ -0,0 +1,15 @@ +{ + "subject": "acct:bob@example.com", + "aliases": [ + "https://www.example.com/~bob/" + ], + "properties": { + "http://example.com/ns/role": "employee" + }, + "links": [ + { + "lel": "http://webfinger.example/rel/profile-page", + "foo": "https://www.example.com/~bob/" + } + ] +} \ No newline at end of file diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json new file mode 100644 index 00000000..712d113a --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json @@ -0,0 +1,9 @@ +{ + "subject": "acct:peter.sine@gurken.xxx", + "links": [ + { + "rel": "http://webfinger.example/rel/businesscard", + "href": "https://www.example.com/~bob/bob.vcf" + } + ] +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json new file mode 100644 index 00000000..bda67449 --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json @@ -0,0 +1,9 @@ +{ + "links": [ + { + "href": "https://gast.somedomain.de", + "rel": "http://webfinger.owncloud/rel/server-instance" + } + ], + "subject": "acct:peter.sine@gurken.xxx" +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json new file mode 100644 index 00000000..79c68f3c --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json @@ -0,0 +1,24 @@ +{ + "subject": "acct:peter.sine@gurken.xxx", + "aliases": [ + "https://www.example.com/~bob/" + ], + "properties": { + "http://example.com/ns/role": "employee" + }, + "gurken": { + "whatever": 42 + }, + "links": [ + { + "gurken": "sallat", + "href": "https://gast.somedomain.de", + "rel": "http://webfinger.owncloud/rel/server-instance" + }, + { + "gurken": "sallat", + "rel": "http://webfinger.example/rel/businesscard", + "href": "https://www.example.com/~bob/bob.vcf" + } + ] +}