mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-10-31 10:27:45 +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: 'kotlin-android' | ||||
| apply plugin: 'kotlin-kapt' | ||||
| apply plugin: 'kotlin-parcelize' | ||||
| 
 | ||||
| dependencies { | ||||
|     api 'com.squareup.okhttp3:okhttp:4.6.0' | ||||
| @ -16,7 +17,7 @@ dependencies { | ||||
|     kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" | ||||
| 
 | ||||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     testImplementation 'org.robolectric:robolectric:4.8.1' | ||||
|     testImplementation 'org.robolectric:robolectric:4.9' | ||||
|     debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -36,7 +36,6 @@ import com.owncloud.android.lib.common.http.HttpClient; | ||||
| import com.owncloud.android.lib.common.http.HttpConstants; | ||||
| import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; | ||||
| import com.owncloud.android.lib.common.utils.RandomUtils; | ||||
| import com.owncloud.android.lib.resources.status.OwnCloudVersion; | ||||
| import okhttp3.Cookie; | ||||
| import okhttp3.HttpUrl; | ||||
| import timber.log.Timber; | ||||
| @ -59,7 +58,6 @@ public class OwnCloudClient extends HttpClient { | ||||
|     private OwnCloudCredentials mCredentials = null; | ||||
|     private int mInstanceNumber; | ||||
|     private Uri mBaseUri; | ||||
|     private OwnCloudVersion mVersion = null; | ||||
|     private OwnCloudAccount mAccount; | ||||
|     private final ConnectionValidator mConnectionValidator; | ||||
|     private Object mRequestMutex = new Object(); | ||||
| @ -241,14 +239,6 @@ public class OwnCloudClient extends HttpClient { | ||||
|                 HttpUrl.parse(mBaseUri.toString())); | ||||
|     } | ||||
| 
 | ||||
|     public OwnCloudVersion getOwnCloudVersion() { | ||||
|         return mVersion; | ||||
|     } | ||||
| 
 | ||||
|     public void setOwnCloudVersion(OwnCloudVersion version) { | ||||
|         mVersion = version; | ||||
|     } | ||||
| 
 | ||||
|     public OwnCloudAccount getAccount() { | ||||
|         return mAccount; | ||||
|     } | ||||
|  | ||||
| @ -94,26 +94,6 @@ public class AccountUtils { | ||||
|         return username; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the stored server version corresponding to an OC account. | ||||
|      * | ||||
|      * @param account An OC account | ||||
|      * @param context Application context | ||||
|      * @return Version of the OC server, according to last check | ||||
|      */ | ||||
|     public static OwnCloudVersion getServerVersionForAccount(Account account, Context context) { | ||||
|         AccountManager ama = AccountManager.get(context); | ||||
|         OwnCloudVersion version = null; | ||||
|         try { | ||||
|             String versionString = ama.getUserData(account, Constants.KEY_OC_VERSION); | ||||
|             version = new OwnCloudVersion(versionString); | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|             Timber.e(e, "Couldn't get a the server version for an account"); | ||||
|         } | ||||
|         return version; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return | ||||
|      * @throws IOException | ||||
| @ -209,11 +189,6 @@ public class AccountUtils { | ||||
|     } | ||||
| 
 | ||||
|     public static class Constants { | ||||
|         /** | ||||
|          * Version should be 3 numbers separated by dot so it can be parsed by | ||||
|          * {@link OwnCloudVersion} | ||||
|          */ | ||||
|         public static final String KEY_OC_VERSION = "oc_version"; | ||||
|         /** | ||||
|          * Base url should point to owncloud installation without trailing / ie: | ||||
|          * http://server/path or https://owncloud.server | ||||
|  | ||||
| @ -184,6 +184,7 @@ public class HttpConstants { | ||||
|     public static final int HTTP_LOCKED = 423; | ||||
|     // 424 Failed Dependency (WebDAV - RFC 2518) | ||||
|     public static final int HTTP_FAILED_DEPENDENCY = 424; | ||||
|     public static final int HTTP_TOO_EARLY = 425; | ||||
| 
 | ||||
|     /** | ||||
|      * 5xx Client Error | ||||
|  | ||||
| @ -36,7 +36,7 @@ import java.net.URL | ||||
| class CopyMethod( | ||||
|     val url: URL, | ||||
|     private val destinationUrl: String, | ||||
|     private val forceOverride: Boolean | ||||
|     private val forceOverride: Boolean = false | ||||
| ) : DavMethod(url) { | ||||
|     @Throws(Exception::class) | ||||
|     public override fun onDavExecute(davResource: DavOCResource): Int { | ||||
|  | ||||
| @ -36,7 +36,7 @@ import java.net.URL | ||||
| class MoveMethod( | ||||
|     url: URL, | ||||
|     private val destinationUrl: String, | ||||
|     private val forceOverride: Boolean | ||||
|     private val forceOverride: Boolean = false | ||||
| ) : DavMethod(url) { | ||||
|     @Throws(Exception::class) | ||||
|     override fun onDavExecute(davResource: DavOCResource): Int { | ||||
|  | ||||
| @ -53,7 +53,7 @@ class PropfindMethod( | ||||
|             depth = depth, | ||||
|             reqProp = propertiesToRequest, | ||||
|             listOfHeaders = super.getRequestHeadersAsHashMap(), | ||||
|             callback = { response: Response, hrefRelation: HrefRelation? -> | ||||
|             callback = { response: Response, hrefRelation: HrefRelation -> | ||||
|                 when (hrefRelation) { | ||||
|                     HrefRelation.MEMBER -> members.add(response) | ||||
|                     HrefRelation.SELF -> this.root = response | ||||
|  | ||||
| @ -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(), | ||||
|                         ResultCode.SPECIFIC_METHOD_NOT_ALLOWED | ||||
|                 ); | ||||
|                 break; | ||||
|             case HttpConstants.HTTP_TOO_EARLY: | ||||
|                 mCode = ResultCode.TOO_EARLY; | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
| @ -583,6 +587,7 @@ public class RemoteOperationResult<T> | ||||
|         SPECIFIC_SERVICE_UNAVAILABLE, | ||||
|         SPECIFIC_UNSUPPORTED_MEDIA_TYPE, | ||||
|         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; | ||||
| 
 | ||||
| /** | ||||
|  * 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; | ||||
|     } | ||||
| fun Any.isOneOf(vararg values: Any): Boolean { | ||||
|     return this in values | ||||
| } | ||||
| @ -36,7 +36,7 @@ data class CommonOcsResponse<T>( | ||||
| @JsonClass(generateAdapter = true) | ||||
| data class OCSResponse<T>( | ||||
|     val meta: MetaData, | ||||
|     val data: T | ||||
|     val data: T? | ||||
| ) | ||||
| 
 | ||||
| @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 MIME_DIR = "DIR"; | ||||
|     public static final String MIME_DIR_UNIX = "httpd/unix-directory"; | ||||
|     public static final String MODE_READ_ONLY = "r"; | ||||
| 
 | ||||
|     static String getParentPath(String remotePath) { | ||||
|         String parentPath = new File(remotePath).getParent(); | ||||
|  | ||||
| @ -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 | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  *   Copyright (C) 2022 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
| @ -24,22 +24,15 @@ | ||||
| 
 | ||||
| package com.owncloud.android.lib.resources.files | ||||
| 
 | ||||
| import android.content.ContentResolver | ||||
| import android.net.Uri | ||||
| import android.provider.OpenableColumns | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.http.HttpConstants | ||||
| import com.owncloud.android.lib.common.http.methods.webdav.PutMethod | ||||
| import com.owncloud.android.lib.common.network.ContentUriRequestBody | ||||
| import com.owncloud.android.lib.common.network.WebdavUtils | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperation | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import okhttp3.MediaType | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| import okhttp3.RequestBody | ||||
| import okio.BufferedSink | ||||
| import okio.source | ||||
| import com.owncloud.android.lib.common.utils.isOneOf | ||||
| import timber.log.Timber | ||||
| import java.io.IOException | ||||
| import java.net.URL | ||||
| 
 | ||||
| class UploadFileFromContentUriOperation( | ||||
| @ -70,34 +63,6 @@ class UploadFileFromContentUriOperation( | ||||
|     } | ||||
| 
 | ||||
|     fun isSuccess(status: Int): Boolean { | ||||
|         return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class ContentUriRequestBody( | ||||
|     private val contentResolver: ContentResolver, | ||||
|     private val contentUri: Uri | ||||
| ) : RequestBody() { | ||||
| 
 | ||||
|     override fun contentType(): MediaType? { | ||||
|         val contentType = contentResolver.getType(contentUri) ?: return null | ||||
|         return contentType.toMediaTypeOrNull() | ||||
|     } | ||||
| 
 | ||||
|     override fun contentLength(): Long { | ||||
|         contentResolver.query(contentUri, null, null, null, null)?.use { cursor -> | ||||
|             val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) | ||||
|             cursor.moveToFirst() | ||||
|             return cursor.getLong(sizeIndex) | ||||
|         } ?: return -1 | ||||
|     } | ||||
| 
 | ||||
|     override fun writeTo(sink: BufferedSink) { | ||||
|         val inputStream = contentResolver.openInputStream(contentUri) | ||||
|             ?: throw IOException("Couldn't open content URI for reading: $contentUri") | ||||
| 
 | ||||
|         inputStream.source().use { source -> | ||||
|             sink.writeAll(source) | ||||
|         } | ||||
|         return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  *   Copyright (C) 2020 ownCloud GmbH. | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
| @ -21,30 +21,38 @@ | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib.resources.files.chunks | ||||
| 
 | ||||
| package com.owncloud.android.lib.resources.files.chunks; | ||||
| 
 | ||||
| import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation; | ||||
| import android.net.Uri | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.http.HttpConstants | ||||
| import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod | ||||
| import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation | ||||
| 
 | ||||
| /** | ||||
|  * Remote operation to move the file built from chunks after uploading it | ||||
|  * | ||||
|  * @author David González Verdugo | ||||
|  * @author Abel García de Prada | ||||
|  */ | ||||
| public class MoveRemoteChunksFileOperation extends MoveRemoteFileOperation { | ||||
| class MoveRemoteChunksFileOperation( | ||||
|     sourceRemotePath: String, | ||||
|     targetRemotePath: String, | ||||
|     private val fileLastModificationTimestamp: String, | ||||
|     private val fileLength: Long | ||||
| ) : MoveRemoteFileOperation( | ||||
|     sourceRemotePath = sourceRemotePath, | ||||
|     targetRemotePath = targetRemotePath, | ||||
| ) { | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor. | ||||
|      * | ||||
|      * @param srcRemotePath    Remote path of the file/folder to move. | ||||
|      * @param targetRemotePath Remove path desired for the file/folder after moving it. | ||||
|      * @param overwrite | ||||
|      */ | ||||
|     public MoveRemoteChunksFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite, | ||||
|                                          String fileLastModifTimestamp, long fileLength) { | ||||
|         super(srcRemotePath, targetRemotePath, overwrite); | ||||
|         moveChunkedFile = true; | ||||
|         mFileLastModifTimestamp = fileLastModifTimestamp; | ||||
|         mFileLength = fileLength; | ||||
|     override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri | ||||
| 
 | ||||
|     override fun addRequestHeaders(moveMethod: MoveMethod) { | ||||
|         super.addRequestHeaders(moveMethod) | ||||
| 
 | ||||
|         moveMethod.apply { | ||||
|             addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, fileLastModificationTimestamp) | ||||
|             addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileLength.toString()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -22,20 +22,12 @@ | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib.resources.files.chunks | ||||
| 
 | ||||
| package com.owncloud.android.lib.resources.files.chunks; | ||||
| import android.net.Uri | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation | ||||
| 
 | ||||
| import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation; | ||||
| 
 | ||||
| public class RemoveRemoteChunksFolderOperation extends RemoveRemoteFileOperation { | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor | ||||
|      * | ||||
|      * @param remotePath RemotePath of the remote file or folder to remove from the server | ||||
|      */ | ||||
|     public RemoveRemoteChunksFolderOperation(String remotePath) { | ||||
|         super(remotePath); | ||||
|         removeChunksFolder = true; | ||||
|     } | ||||
| class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) { | ||||
|     override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri | ||||
| } | ||||
| @ -1,5 +1,5 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
|  *   Copyright (C) 2020 ownCloud GmbH. | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
| @ -21,29 +21,21 @@ | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib.resources.files | ||||
| 
 | ||||
| import android.net.Uri | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import okhttp3.HttpUrl | ||||
| package com.owncloud.android.lib.resources.files.services | ||||
| 
 | ||||
| class RemoteFileUtil { | ||||
|     companion object { | ||||
|         /** | ||||
|          * Retrieves a relative path from a remote file url | ||||
|          * | ||||
|          * | ||||
|          * Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt | ||||
|          * | ||||
|          * @param url    remote file url | ||||
|          * @param userId file owner | ||||
|          * @return remote relative path of the file | ||||
|          */ | ||||
|         fun getRemotePathFromUrl(url: HttpUrl, userId: String): String? { | ||||
|             val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId | ||||
|             val absoluteDavPath = Uri.decode(url.encodedPath) | ||||
|             val pathToOc = absoluteDavPath.split(davFilesPath)[0] | ||||
|             return absoluteDavPath.replace(pathToOc + davFilesPath, "") | ||||
|         } | ||||
|     } | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.Service | ||||
| 
 | ||||
| interface ChunkService : Service { | ||||
|     fun removeFile( | ||||
|         remotePath: String | ||||
|     ): RemoteOperationResult<Unit> | ||||
| 
 | ||||
|     fun moveFile( | ||||
|         sourceRemotePath: String, | ||||
|         targetRemotePath: String, | ||||
|         fileLastModificationTimestamp: String, | ||||
|         fileLength: Long | ||||
|     ): RemoteOperationResult<Unit> | ||||
| } | ||||
| @ -25,8 +25,53 @@ package com.owncloud.android.lib.resources.files.services | ||||
| 
 | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.Service | ||||
| import com.owncloud.android.lib.resources.files.RemoteFile | ||||
| 
 | ||||
| interface FileService : Service { | ||||
|     fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> | ||||
|     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.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation | ||||
| import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation | ||||
| import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.GetUrlToOpenInWebRemoteOperation | ||||
| import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation | ||||
| import com.owncloud.android.lib.resources.files.RemoteFile | ||||
| import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation | ||||
| import com.owncloud.android.lib.resources.files.services.FileService | ||||
| 
 | ||||
| class OCFileService(override val client: OwnCloudClient) : | ||||
|     FileService { | ||||
|     override fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> = | ||||
| class OCFileService(override val client: OwnCloudClient) : FileService { | ||||
| 
 | ||||
|     override fun checkPathExistence( | ||||
|         path: String, | ||||
|         isUserLogged: Boolean | ||||
|     ): RemoteOperationResult<Boolean> = | ||||
|         CheckPathExistenceRemoteOperation( | ||||
|             remotePath = path, | ||||
|             isUserLoggedIn = isUserLogged | ||||
| @ -40,4 +52,75 @@ class OCFileService(override val client: OwnCloudClient) : | ||||
|     override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String): RemoteOperationResult<String> = | ||||
|         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()) | ||||
|             .build() | ||||
| 
 | ||||
|     private fun parseResponse(response: String): ShareeOcsResponse? { | ||||
|     private fun parseResponse(response: String?): ShareeOcsResponse? { | ||||
|         val moshi = Moshi.Builder().build() | ||||
|         val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java) | ||||
|         val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type) | ||||
|         return adapter.fromJson(response)!!.ocs.data | ||||
|         return response?.let { adapter.fromJson(it)?.ocs?.data } | ||||
|     } | ||||
| 
 | ||||
|     private fun onResultUnsuccessful( | ||||
| @ -123,7 +123,7 @@ class GetRemoteShareesOperation | ||||
|     private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> { | ||||
|         val result = RemoteOperationResult<ShareeOcsResponse>(OK) | ||||
|         Timber.d("Successful response: $response") | ||||
|         result.data = parseResponse(response!!) | ||||
|         result.data = parseResponse(response) | ||||
|         Timber.d("*** Get Users or groups completed ") | ||||
|         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 | ||||
|     fun `example response - ok - correct sturcture`() { | ||||
|         val response = loadResponses(EXAMPLE_RESPONSE_JSON)!! | ||||
|         assertEquals(2, response.ocs.data.groups.size) | ||||
|         assertEquals(0, response.ocs.data.remotes.size) | ||||
|         assertEquals(2, response.ocs.data.users.size) | ||||
|         assertEquals(0, response.ocs.data.exact?.groups?.size) | ||||
|         assertEquals(0, response.ocs.data.exact?.remotes?.size) | ||||
|         assertEquals(1, response.ocs.data.exact?.users?.size) | ||||
|         assertEquals("user1@user1.com", response.ocs.data.users.get(0).value.additionalInfo) | ||||
|         assertNull(response.ocs.data.users[1].value.additionalInfo) | ||||
|         assertEquals(2, response.ocs.data?.groups?.size) | ||||
|         assertEquals(0, response.ocs.data?.remotes?.size) | ||||
|         assertEquals(2, response.ocs.data?.users?.size) | ||||
|         assertEquals(0, response.ocs.data?.exact?.groups?.size) | ||||
|         assertEquals(0, response.ocs.data?.exact?.remotes?.size) | ||||
|         assertEquals(1, response.ocs.data?.exact?.users?.size) | ||||
|         assertEquals("user1@user1.com", response.ocs.data?.users?.get(0)?.value?.additionalInfo) | ||||
|         assertNull(response.ocs.data?.users?.get(1)?.value?.additionalInfo) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check empty response - ok - parsing ok`() { | ||||
|         val response = loadResponses(EMPTY_RESPONSE_JSON)!! | ||||
|         assertTrue(response.ocs.data.exact?.groups?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data.exact?.remotes?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data.exact?.users?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data.groups.isEmpty()) | ||||
|         assertTrue(response.ocs.data.remotes.isEmpty()) | ||||
|         assertTrue(response.ocs.data.users.isEmpty()) | ||||
|         assertTrue(response.ocs.data?.exact?.groups?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data?.exact?.remotes?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data?.exact?.users?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data?.groups?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data?.remotes?.isEmpty()!!) | ||||
|         assertTrue(response.ocs.data?.users?.isEmpty()!!) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         val RESOURCES_PATH = | ||||
|         private const val RESOURCES_PATH = | ||||
|             "src/test/responses/com.owncloud.android.lib.resources.sharees.responses" | ||||
|         val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" | ||||
|         val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" | ||||
|         const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" | ||||
|         const val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" | ||||
|     } | ||||
| } | ||||
| @ -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