mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-10-31 02:17:41 +00:00 
			
		
		
		
	Merge pull request #521 from owncloud/master
Merge master into stable for 3.0 app version
This commit is contained in:
		
						commit
						a788dc098b
					
				| @ -1,6 +1,7 @@ | |||||||
| apply plugin: 'com.android.library' | apply plugin: 'com.android.library' | ||||||
| apply plugin: 'kotlin-android' | apply plugin: 'kotlin-android' | ||||||
| apply plugin: 'kotlin-kapt' | apply plugin: 'kotlin-kapt' | ||||||
|  | apply plugin: 'kotlin-parcelize' | ||||||
| 
 | 
 | ||||||
| dependencies { | dependencies { | ||||||
|     api 'com.squareup.okhttp3:okhttp:4.6.0' |     api 'com.squareup.okhttp3:okhttp:4.6.0' | ||||||
| @ -16,7 +17,7 @@ dependencies { | |||||||
|     kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" |     kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" | ||||||
| 
 | 
 | ||||||
|     testImplementation 'junit:junit:4.13.2' |     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' |     debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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.HttpConstants; | ||||||
| import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; | import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; | ||||||
| import com.owncloud.android.lib.common.utils.RandomUtils; | import com.owncloud.android.lib.common.utils.RandomUtils; | ||||||
| import com.owncloud.android.lib.resources.status.OwnCloudVersion; |  | ||||||
| import okhttp3.Cookie; | import okhttp3.Cookie; | ||||||
| import okhttp3.HttpUrl; | import okhttp3.HttpUrl; | ||||||
| import timber.log.Timber; | import timber.log.Timber; | ||||||
| @ -59,7 +58,6 @@ public class OwnCloudClient extends HttpClient { | |||||||
|     private OwnCloudCredentials mCredentials = null; |     private OwnCloudCredentials mCredentials = null; | ||||||
|     private int mInstanceNumber; |     private int mInstanceNumber; | ||||||
|     private Uri mBaseUri; |     private Uri mBaseUri; | ||||||
|     private OwnCloudVersion mVersion = null; |  | ||||||
|     private OwnCloudAccount mAccount; |     private OwnCloudAccount mAccount; | ||||||
|     private final ConnectionValidator mConnectionValidator; |     private final ConnectionValidator mConnectionValidator; | ||||||
|     private Object mRequestMutex = new Object(); |     private Object mRequestMutex = new Object(); | ||||||
| @ -241,14 +239,6 @@ public class OwnCloudClient extends HttpClient { | |||||||
|                 HttpUrl.parse(mBaseUri.toString())); |                 HttpUrl.parse(mBaseUri.toString())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public OwnCloudVersion getOwnCloudVersion() { |  | ||||||
|         return mVersion; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setOwnCloudVersion(OwnCloudVersion version) { |  | ||||||
|         mVersion = version; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public OwnCloudAccount getAccount() { |     public OwnCloudAccount getAccount() { | ||||||
|         return mAccount; |         return mAccount; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -94,26 +94,6 @@ public class AccountUtils { | |||||||
|         return username; |         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 |      * @return | ||||||
|      * @throws IOException |      * @throws IOException | ||||||
| @ -209,11 +189,6 @@ public class AccountUtils { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static class Constants { |     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: |          * Base url should point to owncloud installation without trailing / ie: | ||||||
|          * http://server/path or https://owncloud.server |          * http://server/path or https://owncloud.server | ||||||
|  | |||||||
| @ -184,6 +184,7 @@ public class HttpConstants { | |||||||
|     public static final int HTTP_LOCKED = 423; |     public static final int HTTP_LOCKED = 423; | ||||||
|     // 424 Failed Dependency (WebDAV - RFC 2518) |     // 424 Failed Dependency (WebDAV - RFC 2518) | ||||||
|     public static final int HTTP_FAILED_DEPENDENCY = 424; |     public static final int HTTP_FAILED_DEPENDENCY = 424; | ||||||
|  |     public static final int HTTP_TOO_EARLY = 425; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 5xx Client Error |      * 5xx Client Error | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ import java.net.URL | |||||||
| class CopyMethod( | class CopyMethod( | ||||||
|     val url: URL, |     val url: URL, | ||||||
|     private val destinationUrl: String, |     private val destinationUrl: String, | ||||||
|     private val forceOverride: Boolean |     private val forceOverride: Boolean = false | ||||||
| ) : DavMethod(url) { | ) : DavMethod(url) { | ||||||
|     @Throws(Exception::class) |     @Throws(Exception::class) | ||||||
|     public override fun onDavExecute(davResource: DavOCResource): Int { |     public override fun onDavExecute(davResource: DavOCResource): Int { | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ import java.net.URL | |||||||
| class MoveMethod( | class MoveMethod( | ||||||
|     url: URL, |     url: URL, | ||||||
|     private val destinationUrl: String, |     private val destinationUrl: String, | ||||||
|     private val forceOverride: Boolean |     private val forceOverride: Boolean = false | ||||||
| ) : DavMethod(url) { | ) : DavMethod(url) { | ||||||
|     @Throws(Exception::class) |     @Throws(Exception::class) | ||||||
|     override fun onDavExecute(davResource: DavOCResource): Int { |     override fun onDavExecute(davResource: DavOCResource): Int { | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ class PropfindMethod( | |||||||
|             depth = depth, |             depth = depth, | ||||||
|             reqProp = propertiesToRequest, |             reqProp = propertiesToRequest, | ||||||
|             listOfHeaders = super.getRequestHeadersAsHashMap(), |             listOfHeaders = super.getRequestHeadersAsHashMap(), | ||||||
|             callback = { response: Response, hrefRelation: HrefRelation? -> |             callback = { response: Response, hrefRelation: HrefRelation -> | ||||||
|                 when (hrefRelation) { |                 when (hrefRelation) { | ||||||
|                     HrefRelation.MEMBER -> members.add(response) |                     HrefRelation.MEMBER -> members.add(response) | ||||||
|                     HrefRelation.SELF -> this.root = response |                     HrefRelation.SELF -> this.root = response | ||||||
|  | |||||||
| @ -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<OnDatatransferProgressListener> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<OnDatatransferProgressListener> | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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<OnDatatransferProgressListener> = 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<OnDatatransferProgressListener> | ||||||
|  | 
 | ||||||
|  |         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<OnDatatransferProgressListener>) { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<OnDatatransferProgressListener> 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<OnDatatransferProgressListener> 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<OnDatatransferProgressListener> listeners) { |  | ||||||
|         synchronized (mDataTransferListeners) { |  | ||||||
|             mDataTransferListeners.addAll(listeners); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { |  | ||||||
|         synchronized (mDataTransferListeners) { |  | ||||||
|             mDataTransferListeners.remove(listener); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<OnDatatransferProgressListener> = 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<OnDatatransferProgressListener> | ||||||
|  |         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<OnDatatransferProgressListener>) { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -237,6 +237,10 @@ public class RemoteOperationResult<T> | |||||||
|                         httpMethod.getResponseBodyAsString(), |                         httpMethod.getResponseBodyAsString(), | ||||||
|                         ResultCode.SPECIFIC_METHOD_NOT_ALLOWED |                         ResultCode.SPECIFIC_METHOD_NOT_ALLOWED | ||||||
|                 ); |                 ); | ||||||
|  |                 break; | ||||||
|  |             case HttpConstants.HTTP_TOO_EARLY: | ||||||
|  |                 mCode = ResultCode.TOO_EARLY; | ||||||
|  |                 break; | ||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
| @ -583,6 +587,7 @@ public class RemoteOperationResult<T> | |||||||
|         SPECIFIC_SERVICE_UNAVAILABLE, |         SPECIFIC_SERVICE_UNAVAILABLE, | ||||||
|         SPECIFIC_UNSUPPORTED_MEDIA_TYPE, |         SPECIFIC_UNSUPPORTED_MEDIA_TYPE, | ||||||
|         SPECIFIC_METHOD_NOT_ALLOWED, |         SPECIFIC_METHOD_NOT_ALLOWED, | ||||||
|         SPECIFIC_BAD_REQUEST |         SPECIFIC_BAD_REQUEST, | ||||||
|  |         TOO_EARLY, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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; | fun Any.isOneOf(vararg values: Any): Boolean { | ||||||
| 
 |     return this in values | ||||||
| /** |  | ||||||
|  * 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; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -36,7 +36,7 @@ data class CommonOcsResponse<T>( | |||||||
| @JsonClass(generateAdapter = true) | @JsonClass(generateAdapter = true) | ||||||
| data class OCSResponse<T>( | data class OCSResponse<T>( | ||||||
|     val meta: MetaData, |     val meta: MetaData, | ||||||
|     val data: T |     val data: T? | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @JsonClass(generateAdapter = true) | @JsonClass(generateAdapter = true) | ||||||
|  | |||||||
| @ -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<String> { |  | ||||||
| 
 |  | ||||||
|     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. |  | ||||||
|      * <p/> |  | ||||||
|      * 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<String> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<String>() { | ||||||
|  |     /** | ||||||
|  |      * Performs the rename operation. | ||||||
|  |      * | ||||||
|  |      * @param client Client object to communicate with the remote ownCloud server. | ||||||
|  |      */ | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<String> { | ||||||
|  |         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<String> | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { | ||||||
|  | 
 | ||||||
|  |         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<Unit> { | ||||||
|  |         var result: RemoteOperationResult<Unit> | ||||||
|  |         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<Unit> { | ||||||
|  |         val operation: RemoteOperation<Unit> = 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<OnDatatransferProgressListener> 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<OnDatatransferProgressListener> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     private val cancellationRequested = AtomicBoolean(false) | ||||||
|  |     private val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet() | ||||||
|  | 
 | ||||||
|  |     var modificationTimestamp: Long = 0 | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     var etag: String = "" | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { | ||||||
|  |         // 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<Unit>(e).also { result -> | ||||||
|  |                 Timber.e(e, "Download of $remotePath to $tmpPath: ${result.logMessage}") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(Exception::class) | ||||||
|  |     private fun downloadFile(client: OwnCloudClient, targetFile: File): RemoteOperationResult<Unit> { | ||||||
|  |         val result: RemoteOperationResult<Unit> | ||||||
|  |         var it: Iterator<OnDatatransferProgressListener> | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -32,6 +32,7 @@ public class FileUtils { | |||||||
|     public static final String FINAL_CHUNKS_FILE = ".file"; |     public static final String FINAL_CHUNKS_FILE = ".file"; | ||||||
|     public static final String MIME_DIR = "DIR"; |     public static final String MIME_DIR = "DIR"; | ||||||
|     public static final String MIME_DIR_UNIX = "httpd/unix-directory"; |     public static final String MIME_DIR_UNIX = "httpd/unix-directory"; | ||||||
|  |     public static final String MODE_READ_ONLY = "r"; | ||||||
| 
 | 
 | ||||||
|     static String getParentPath(String remotePath) { |     static String getParentPath(String remotePath) { | ||||||
|         String parentPath = new File(remotePath).getParent(); |         String parentPath = new File(remotePath).getParent(); | ||||||
|  | |||||||
| @ -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. |  | ||||||
|  * <p> |  | ||||||
|  * 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. |  | ||||||
|      * <p> |  | ||||||
|      * 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Performs the rename operation. | ||||||
|  |      * | ||||||
|  |      * @param client Client object to communicate with the remote ownCloud server. | ||||||
|  |      */ | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { | ||||||
|  |         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<Unit> | ||||||
|  |         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<Unit>(ResultCode.OK) | ||||||
|  |                 } | ||||||
|  |                 isPreconditionFailed(status) -> { | ||||||
|  |                     result = RemoteOperationResult<Unit>(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<Unit>(moveMethod) | ||||||
|  |                     client.exhaustResponse(moveMethod.getResponseBodyAsStream()) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Timber.i("Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}") | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             result = RemoteOperationResult<Unit>(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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<RemoteFile> { |  | ||||||
| 
 |  | ||||||
|     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<RemoteFile> run(OwnCloudClient client) { |  | ||||||
|         PropfindMethod propfind; |  | ||||||
|         RemoteOperationResult<RemoteFile> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<RemoteFile>() { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Performs the read operation. | ||||||
|  |      * | ||||||
|  |      * @param client Client object to communicate with the remote ownCloud server. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteFile> { | ||||||
|  |         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<RemoteFile>(RemoteOperationResult.ResultCode.OK).apply { | ||||||
|  |                     data = remoteFile | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 RemoteOperationResult<RemoteFile>(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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<ArrayList<RemoteFile>> { |  | ||||||
| 
 |  | ||||||
|     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<ArrayList<RemoteFile>> run(OwnCloudClient client) { |  | ||||||
|         RemoteOperationResult<ArrayList<RemoteFile>> 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<RemoteFile> 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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<ArrayList<RemoteFile>>() { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Performs the read operation. | ||||||
|  |      * | ||||||
|  |      * @param client Client object to communicate with the remote ownCloud server. | ||||||
|  |      */ | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<ArrayList<RemoteFile>> { | ||||||
|  |         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<RemoteFile>() | ||||||
|  | 
 | ||||||
|  |                 // 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<ArrayList<RemoteFile>>(ResultCode.OK).apply { | ||||||
|  |                     data = mFolderAndFiles | ||||||
|  |                     Timber.i("Synchronized $remotePath with ${mFolderAndFiles.size} files. ${this.logMessage}") | ||||||
|  |                 } | ||||||
|  |             } else { // synchronization failed | ||||||
|  |                 return RemoteOperationResult<ArrayList<RemoteFile>>(propfindMethod).also { | ||||||
|  |                     Timber.w("Synchronized $remotePath ${it.logMessage}") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             return RemoteOperationResult<ArrayList<RemoteFile>>(e).also { | ||||||
|  |                 Timber.e(it.exception, "Synchronized $remotePath") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS) | ||||||
|  | } | ||||||
| @ -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<RemoteFile> CREATOR = new Parcelable.Creator<RemoteFile>() { |  | ||||||
|         @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. |  | ||||||
|      * <p> |  | ||||||
|      * 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<Property> 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<String> 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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Property> { | ||||||
|  |             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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { | ||||||
|  |         var result: RemoteOperationResult<Unit> | ||||||
|  |         try { | ||||||
|  |             val srcWebDavUri = getSrcWebDavUriForClient(client) | ||||||
|  |             val deleteMethod = DeleteMethod( | ||||||
|  |                 URL(srcWebDavUri.toString() + WebdavUtils.encodePath(remotePath)) | ||||||
|  |             ) | ||||||
|  |             val status = client.executeHttpMethod(deleteMethod) | ||||||
|  | 
 | ||||||
|  |             result = if (isSuccess(status)) { | ||||||
|  |                 RemoteOperationResult<Unit>(ResultCode.OK) | ||||||
|  |             } else { | ||||||
|  |                 RemoteOperationResult<Unit>(deleteMethod) | ||||||
|  |             } | ||||||
|  |             Timber.i("Remove $remotePath: ${result.logMessage}") | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             result = RemoteOperationResult<Unit>(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) | ||||||
|  | } | ||||||
| @ -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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     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<Unit> { | ||||||
|  |         var result: RemoteOperationResult<Unit> | ||||||
|  |         try { | ||||||
|  |             if (newName == oldName) { | ||||||
|  |                 return RemoteOperationResult<Unit>(ResultCode.OK) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (targetPathIsUsed(client)) { | ||||||
|  |                 return RemoteOperationResult<Unit>(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<Unit>(ResultCode.OK) | ||||||
|  |             } else { | ||||||
|  |                 RemoteOperationResult<Unit>(moveMethod) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Timber.i("Rename $oldRemotePath to $newRemotePath: ${result.logMessage}") | ||||||
|  |             client.exhaustResponse(moveMethod.getResponseBodyAsStream()) | ||||||
|  |             return result | ||||||
|  |         } catch (exception: Exception) { | ||||||
|  |             result = RemoteOperationResult<Unit>(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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,5 +1,5 @@ | |||||||
| /* ownCloud Android Library is available under MIT license | /* ownCloud Android Library is available under MIT license | ||||||
|  *   Copyright (C) 2021 ownCloud GmbH. |  *   Copyright (C) 2022 ownCloud GmbH. | ||||||
|  * |  * | ||||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy |  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  *   of this software and associated documentation files (the "Software"), to deal |  *   of this software and associated documentation files (the "Software"), to deal | ||||||
| @ -24,22 +24,15 @@ | |||||||
| 
 | 
 | ||||||
| package com.owncloud.android.lib.resources.files | 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.OwnCloudClient | ||||||
| import com.owncloud.android.lib.common.http.HttpConstants | import com.owncloud.android.lib.common.http.HttpConstants | ||||||
| import com.owncloud.android.lib.common.http.methods.webdav.PutMethod | 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.network.WebdavUtils | ||||||
| import com.owncloud.android.lib.common.operations.RemoteOperation | import com.owncloud.android.lib.common.operations.RemoteOperation | ||||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||||
| import okhttp3.MediaType | import com.owncloud.android.lib.common.utils.isOneOf | ||||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull |  | ||||||
| import okhttp3.RequestBody |  | ||||||
| import okio.BufferedSink |  | ||||||
| import okio.source |  | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
| import java.io.IOException |  | ||||||
| import java.net.URL | import java.net.URL | ||||||
| 
 | 
 | ||||||
| class UploadFileFromContentUriOperation( | class UploadFileFromContentUriOperation( | ||||||
| @ -70,34 +63,6 @@ class UploadFileFromContentUriOperation( | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun isSuccess(status: Int): Boolean { |     fun isSuccess(status: Int): Boolean { | ||||||
|         return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT |         return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, 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) |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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<Unit>() { | ||||||
|  | 
 | ||||||
|  |     protected val cancellationRequested = AtomicBoolean(false) | ||||||
|  |     protected var putMethod: PutMethod? = null | ||||||
|  |     protected val dataTransferListener: MutableSet<OnDatatransferProgressListener> = HashSet() | ||||||
|  |     protected var fileRequestBody: FileRequestBody? = null | ||||||
|  | 
 | ||||||
|  |     var etag: String = "" | ||||||
|  | 
 | ||||||
|  |     override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> { | ||||||
|  |         var result: RemoteOperationResult<Unit> | ||||||
|  |         try { | ||||||
|  |             if (cancellationRequested.get()) { | ||||||
|  |                 // the operation was cancelled before getting it's turn to be executed in the queue of uploads | ||||||
|  |                 result = RemoteOperationResult<Unit>(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<Unit>(OperationCancelledException()) | ||||||
|  |                 Timber.e(result.exception, "Upload of $localPath to $remotePath has been aborted with this message: ${result.logMessage}") | ||||||
|  |             } else { | ||||||
|  |                 result = RemoteOperationResult<Unit>(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<Unit> { | ||||||
|  |         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<Unit>(ResultCode.OK).apply { data = Unit } | ||||||
|  |         } else { // synchronization failed | ||||||
|  |             RemoteOperationResult<Unit>(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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<OnDatatransferProgressListener> 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<OnDatatransferProgressListener> 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)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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<Unit> { | ||||||
|  |         lateinit var result: RemoteOperationResult<Unit> | ||||||
|  | 
 | ||||||
|  |         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<Unit>(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<Unit>(ResultCode.OK) | ||||||
|  |                 } else { | ||||||
|  |                     result = RemoteOperationResult<Unit>(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. | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,5 +1,5 @@ | |||||||
| /* ownCloud Android Library is available under MIT license | /* ownCloud Android Library is available under MIT license | ||||||
|  *   Copyright (C) 2020 ownCloud GmbH. |  *   Copyright (C) 2021 ownCloud GmbH. | ||||||
|  * |  * | ||||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy |  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  *   of this software and associated documentation files (the "Software"), to deal |  *   of this software and associated documentation files (the "Software"), to deal | ||||||
| @ -21,30 +21,38 @@ | |||||||
|  *   THE SOFTWARE. |  *   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.MoveRemoteFileOperation; | 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 |  * Remote operation to move the file built from chunks after uploading it | ||||||
|  * |  * | ||||||
|  * @author David González Verdugo |  * @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, | ||||||
|  | ) { | ||||||
| 
 | 
 | ||||||
|     /** |     override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri | ||||||
|      * Constructor. | 
 | ||||||
|      * |     override fun addRequestHeaders(moveMethod: MoveMethod) { | ||||||
|      * @param srcRemotePath    Remote path of the file/folder to move. |         super.addRequestHeaders(moveMethod) | ||||||
|      * @param targetRemotePath Remove path desired for the file/folder after moving it. | 
 | ||||||
|      * @param overwrite |         moveMethod.apply { | ||||||
|      */ |             addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, fileLastModificationTimestamp) | ||||||
|     public MoveRemoteChunksFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite, |             addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileLength.toString()) | ||||||
|                                          String fileLastModifTimestamp, long fileLength) { |         } | ||||||
|         super(srcRemotePath, targetRemotePath, overwrite); |  | ||||||
|         moveChunkedFile = true; |  | ||||||
|         mFileLastModifTimestamp = fileLastModifTimestamp; |  | ||||||
|         mFileLength = fileLength; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -22,20 +22,12 @@ | |||||||
|  *   THE SOFTWARE. |  *   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; | class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) { | ||||||
| 
 |     override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri | ||||||
| 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; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -1,5 +1,5 @@ | |||||||
| /* ownCloud Android Library is available under MIT license | /* ownCloud Android Library is available under MIT license | ||||||
|  *   Copyright (C) 2020 ownCloud GmbH. |  *   Copyright (C) 2021 ownCloud GmbH. | ||||||
|  * |  * | ||||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy |  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  *   of this software and associated documentation files (the "Software"), to deal |  *   of this software and associated documentation files (the "Software"), to deal | ||||||
| @ -21,29 +21,21 @@ | |||||||
|  *   THE SOFTWARE. |  *   THE SOFTWARE. | ||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| package com.owncloud.android.lib.resources.files |  | ||||||
| 
 | 
 | ||||||
| import android.net.Uri | package com.owncloud.android.lib.resources.files.services | ||||||
| import com.owncloud.android.lib.common.OwnCloudClient |  | ||||||
| import okhttp3.HttpUrl |  | ||||||
| 
 | 
 | ||||||
| class RemoteFileUtil { | import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||||
|     companion object { | import com.owncloud.android.lib.resources.Service | ||||||
|         /** | 
 | ||||||
|          * Retrieves a relative path from a remote file url | interface ChunkService : Service { | ||||||
|          * |     fun removeFile( | ||||||
|          * |         remotePath: String | ||||||
|          * Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt |     ): RemoteOperationResult<Unit> | ||||||
|          * | 
 | ||||||
|          * @param url    remote file url |     fun moveFile( | ||||||
|          * @param userId file owner |         sourceRemotePath: String, | ||||||
|          * @return remote relative path of the file |         targetRemotePath: String, | ||||||
|          */ |         fileLastModificationTimestamp: String, | ||||||
|         fun getRemotePathFromUrl(url: HttpUrl, userId: String): String? { |         fileLength: Long | ||||||
|             val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId |     ): RemoteOperationResult<Unit> | ||||||
|             val absoluteDavPath = Uri.decode(url.encodedPath) |  | ||||||
|             val pathToOc = absoluteDavPath.split(davFilesPath)[0] |  | ||||||
|             return absoluteDavPath.replace(pathToOc + davFilesPath, "") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -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.common.operations.RemoteOperationResult | ||||||
| import com.owncloud.android.lib.resources.Service | import com.owncloud.android.lib.resources.Service | ||||||
|  | import com.owncloud.android.lib.resources.files.RemoteFile | ||||||
| 
 | 
 | ||||||
| interface FileService : Service { | interface FileService : Service { | ||||||
|     fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> |  | ||||||
|     fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> |     fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> | ||||||
|  | 
 | ||||||
|  |     fun checkPathExistence( | ||||||
|  |         path: String, | ||||||
|  |         isUserLogged: Boolean | ||||||
|  |     ): RemoteOperationResult<Boolean> | ||||||
|  | 
 | ||||||
|  |     fun copyFile( | ||||||
|  |         sourceRemotePath: String, | ||||||
|  |         targetRemotePath: String, | ||||||
|  |     ): RemoteOperationResult<String> | ||||||
|  | 
 | ||||||
|  |     fun createFolder( | ||||||
|  |         remotePath: String, | ||||||
|  |         createFullPath: Boolean, | ||||||
|  |         isChunkFolder: Boolean = false | ||||||
|  |     ): RemoteOperationResult<Unit> | ||||||
|  | 
 | ||||||
|  |     fun downloadFile( | ||||||
|  |         remotePath: String, | ||||||
|  |         localTempPath: String | ||||||
|  |     ): RemoteOperationResult<Unit> | ||||||
|  | 
 | ||||||
|  |     fun moveFile( | ||||||
|  |         sourceRemotePath: String, | ||||||
|  |         targetRemotePath: String, | ||||||
|  |     ): RemoteOperationResult<Unit> | ||||||
|  | 
 | ||||||
|  |     fun readFile( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<RemoteFile> | ||||||
|  | 
 | ||||||
|  |     fun refreshFolder( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<ArrayList<RemoteFile>> | ||||||
|  | 
 | ||||||
|  |     fun removeFile( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<Unit> | ||||||
|  | 
 | ||||||
|  |     fun renameFile( | ||||||
|  |         oldName: String, | ||||||
|  |         oldRemotePath: String, | ||||||
|  |         newName: String, | ||||||
|  |         isFolder: Boolean, | ||||||
|  |     ): RemoteOperationResult<Unit> | ||||||
| } | } | ||||||
|  | |||||||
| @ -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<Unit> = | ||||||
|  |         RemoveRemoteChunksFolderOperation(remotePath = remotePath).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun moveFile( | ||||||
|  |         sourceRemotePath: String, | ||||||
|  |         targetRemotePath: String, | ||||||
|  |         fileLastModificationTimestamp: String, | ||||||
|  |         fileLength: Long | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         MoveRemoteChunksFileOperation( | ||||||
|  |             sourceRemotePath = sourceRemotePath, | ||||||
|  |             targetRemotePath = targetRemotePath, | ||||||
|  |             fileLastModificationTimestamp = fileLastModificationTimestamp, | ||||||
|  |             fileLength = fileLength, | ||||||
|  |         ).execute(client) | ||||||
|  | } | ||||||
| @ -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.OwnCloudClient | ||||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||||
| import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation | import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation | ||||||
|  | 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.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 | import com.owncloud.android.lib.resources.files.services.FileService | ||||||
| 
 | 
 | ||||||
| class OCFileService(override val client: OwnCloudClient) : | class OCFileService(override val client: OwnCloudClient) : FileService { | ||||||
|     FileService { | 
 | ||||||
|     override fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> = |     override fun checkPathExistence( | ||||||
|  |         path: String, | ||||||
|  |         isUserLogged: Boolean | ||||||
|  |     ): RemoteOperationResult<Boolean> = | ||||||
|         CheckPathExistenceRemoteOperation( |         CheckPathExistenceRemoteOperation( | ||||||
|             remotePath = path, |             remotePath = path, | ||||||
|             isUserLoggedIn = isUserLogged |             isUserLoggedIn = isUserLogged | ||||||
| @ -40,4 +52,75 @@ class OCFileService(override val client: OwnCloudClient) : | |||||||
|     override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> = |     override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> = | ||||||
|         GetUrlToOpenInWebRemoteOperation(openWithWebEndpoint = openWebEndpoint, fileId = fileId).execute(client) |         GetUrlToOpenInWebRemoteOperation(openWithWebEndpoint = openWebEndpoint, fileId = fileId).execute(client) | ||||||
| 
 | 
 | ||||||
|  |     override fun copyFile( | ||||||
|  |         sourceRemotePath: String, | ||||||
|  |         targetRemotePath: String | ||||||
|  |     ): RemoteOperationResult<String> = | ||||||
|  |         CopyRemoteFileOperation( | ||||||
|  |             srcRemotePath = sourceRemotePath, | ||||||
|  |             targetRemotePath = targetRemotePath | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun createFolder( | ||||||
|  |         remotePath: String, | ||||||
|  |         createFullPath: Boolean, | ||||||
|  |         isChunkFolder: Boolean | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         CreateRemoteFolderOperation( | ||||||
|  |             remotePath = remotePath, | ||||||
|  |             createFullPath = createFullPath, | ||||||
|  |             isChunksFolder = isChunkFolder | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun downloadFile( | ||||||
|  |         remotePath: String, | ||||||
|  |         localTempPath: String | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         DownloadRemoteFileOperation( | ||||||
|  |             remotePath = remotePath, | ||||||
|  |             localFolderPath = localTempPath | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun moveFile( | ||||||
|  |         sourceRemotePath: String, | ||||||
|  |         targetRemotePath: String, | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         MoveRemoteFileOperation( | ||||||
|  |             sourceRemotePath = sourceRemotePath, | ||||||
|  |             targetRemotePath = targetRemotePath, | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun readFile( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<RemoteFile> = | ||||||
|  |         ReadRemoteFileOperation( | ||||||
|  |             remotePath = remotePath | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun refreshFolder( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<ArrayList<RemoteFile>> = | ||||||
|  |         ReadRemoteFolderOperation( | ||||||
|  |             remotePath = remotePath | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun removeFile( | ||||||
|  |         remotePath: String | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         RemoveRemoteFileOperation( | ||||||
|  |             remotePath = remotePath | ||||||
|  |         ).execute(client) | ||||||
|  | 
 | ||||||
|  |     override fun renameFile( | ||||||
|  |         oldName: String, | ||||||
|  |         oldRemotePath: String, | ||||||
|  |         newName: String, | ||||||
|  |         isFolder: Boolean | ||||||
|  |     ): RemoteOperationResult<Unit> = | ||||||
|  |         RenameRemoteFileOperation( | ||||||
|  |             oldName = oldName, | ||||||
|  |             oldRemotePath = oldRemotePath, | ||||||
|  |             newName = newName, | ||||||
|  |             isFolder = isFolder | ||||||
|  |         ).execute(client) | ||||||
| } | } | ||||||
|  | |||||||
| @ -99,11 +99,11 @@ class GetRemoteShareesOperation | |||||||
|             .appendQueryParameter(PARAM_PER_PAGE, perPage.toString()) |             .appendQueryParameter(PARAM_PER_PAGE, perPage.toString()) | ||||||
|             .build() |             .build() | ||||||
| 
 | 
 | ||||||
|     private fun parseResponse(response: String): ShareeOcsResponse? { |     private fun parseResponse(response: String?): ShareeOcsResponse? { | ||||||
|         val moshi = Moshi.Builder().build() |         val moshi = Moshi.Builder().build() | ||||||
|         val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java) |         val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java) | ||||||
|         val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type) |         val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type) | ||||||
|         return adapter.fromJson(response)!!.ocs.data |         return response?.let { adapter.fromJson(it)?.ocs?.data } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun onResultUnsuccessful( |     private fun onResultUnsuccessful( | ||||||
| @ -123,7 +123,7 @@ class GetRemoteShareesOperation | |||||||
|     private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> { |     private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> { | ||||||
|         val result = RemoteOperationResult<ShareeOcsResponse>(OK) |         val result = RemoteOperationResult<ShareeOcsResponse>(OK) | ||||||
|         Timber.d("Successful response: $response") |         Timber.d("Successful response: $response") | ||||||
|         result.data = parseResponse(response!!) |         result.data = parseResponse(response) | ||||||
|         Timber.d("*** Get Users or groups completed ") |         Timber.d("*** Get Users or groups completed ") | ||||||
|         return result |         return result | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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<String>() { | ||||||
|  | 
 | ||||||
|  |     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<String> { | ||||||
|  |         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<String>(method) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun onRequestSuccessful(rawResponse: String): RemoteOperationResult<String> { | ||||||
|  |         val response = parseResponse(rawResponse) | ||||||
|  |         for (i in response.links) { | ||||||
|  |             if (i.rel == rel) { | ||||||
|  |                 val operationResult = RemoteOperationResult<String>(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<String> { | ||||||
|  |         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<String>(e) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private const val ENDPOINT_WEBFINGER_PATH = "/.well-known/webfinger" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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<LinkItem> | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | @JsonClass(generateAdapter = true) | ||||||
|  | data class LinkItem( | ||||||
|  |     val href: String, | ||||||
|  |     val rel: String | ||||||
|  | ) | ||||||
| @ -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 <http://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  | 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<String> | ||||||
|  | } | ||||||
| @ -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 <http://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  | 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<String> = | ||||||
|  |         GetOCInstanceViaWebfingerOperation(lookupServer, OWNCLOUD_REL, username).execute(client) | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private const val OWNCLOUD_REL = "http://webfinger.owncloud/rel/server-instance" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -63,31 +63,31 @@ class ShareeResponseTest { | |||||||
|     @Test |     @Test | ||||||
|     fun `example response - ok - correct sturcture`() { |     fun `example response - ok - correct sturcture`() { | ||||||
|         val response = loadResponses(EXAMPLE_RESPONSE_JSON)!! |         val response = loadResponses(EXAMPLE_RESPONSE_JSON)!! | ||||||
|         assertEquals(2, response.ocs.data.groups.size) |         assertEquals(2, response.ocs.data?.groups?.size) | ||||||
|         assertEquals(0, response.ocs.data.remotes.size) |         assertEquals(0, response.ocs.data?.remotes?.size) | ||||||
|         assertEquals(2, response.ocs.data.users.size) |         assertEquals(2, response.ocs.data?.users?.size) | ||||||
|         assertEquals(0, response.ocs.data.exact?.groups?.size) |         assertEquals(0, response.ocs.data?.exact?.groups?.size) | ||||||
|         assertEquals(0, response.ocs.data.exact?.remotes?.size) |         assertEquals(0, response.ocs.data?.exact?.remotes?.size) | ||||||
|         assertEquals(1, response.ocs.data.exact?.users?.size) |         assertEquals(1, response.ocs.data?.exact?.users?.size) | ||||||
|         assertEquals("user1@user1.com", response.ocs.data.users.get(0).value.additionalInfo) |         assertEquals("user1@user1.com", response.ocs.data?.users?.get(0)?.value?.additionalInfo) | ||||||
|         assertNull(response.ocs.data.users[1].value.additionalInfo) |         assertNull(response.ocs.data?.users?.get(1)?.value?.additionalInfo) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun `check empty response - ok - parsing ok`() { |     fun `check empty response - ok - parsing ok`() { | ||||||
|         val response = loadResponses(EMPTY_RESPONSE_JSON)!! |         val response = loadResponses(EMPTY_RESPONSE_JSON)!! | ||||||
|         assertTrue(response.ocs.data.exact?.groups?.isEmpty()!!) |         assertTrue(response.ocs.data?.exact?.groups?.isEmpty()!!) | ||||||
|         assertTrue(response.ocs.data.exact?.remotes?.isEmpty()!!) |         assertTrue(response.ocs.data?.exact?.remotes?.isEmpty()!!) | ||||||
|         assertTrue(response.ocs.data.exact?.users?.isEmpty()!!) |         assertTrue(response.ocs.data?.exact?.users?.isEmpty()!!) | ||||||
|         assertTrue(response.ocs.data.groups.isEmpty()) |         assertTrue(response.ocs.data?.groups?.isEmpty()!!) | ||||||
|         assertTrue(response.ocs.data.remotes.isEmpty()) |         assertTrue(response.ocs.data?.remotes?.isEmpty()!!) | ||||||
|         assertTrue(response.ocs.data.users.isEmpty()) |         assertTrue(response.ocs.data?.users?.isEmpty()!!) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     companion object { |     companion object { | ||||||
|         val RESOURCES_PATH = |         private const val RESOURCES_PATH = | ||||||
|             "src/test/responses/com.owncloud.android.lib.resources.sharees.responses" |             "src/test/responses/com.owncloud.android.lib.resources.sharees.responses" | ||||||
|         val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" |         const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" | ||||||
|         val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" |         const val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -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<WebfingerJrdResponse> | ||||||
|  | 
 | ||||||
|  |     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" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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/" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @ -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" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | { | ||||||
|  |   "links": [ | ||||||
|  |     { | ||||||
|  |       "href": "https://gast.somedomain.de", | ||||||
|  |       "rel": "http://webfinger.owncloud/rel/server-instance" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "subject": "acct:peter.sine@gurken.xxx" | ||||||
|  | } | ||||||
| @ -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" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user