1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-08 00:16:09 +00:00

Basic upload with chunks [WIP]

This commit is contained in:
davigonz 2018-06-25 15:32:00 +02:00
parent 366459699e
commit 153c0a10ff
4 changed files with 184 additions and 73 deletions

View File

@ -56,7 +56,7 @@ public class ChunkFromFileChannelRequestEntity implements RequestEntity, Progres
private final File mFile; private final File mFile;
private long mOffset; private long mOffset;
private long mTransferred; private long mTransferred;
Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>(); Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
private ByteBuffer mBuffer = ByteBuffer.allocate(4096); private ByteBuffer mBuffer = ByteBuffer.allocate(4096);
public ChunkFromFileChannelRequestEntity( public ChunkFromFileChannelRequestEntity(
@ -120,8 +120,8 @@ public class ChunkFromFileChannelRequestEntity implements RequestEntity, Progres
public void writeRequest(final OutputStream out) throws IOException { public void writeRequest(final OutputStream out) throws IOException {
int readCount = 0; int readCount;
Iterator<OnDatatransferProgressListener> it = null; Iterator<OnDatatransferProgressListener> it;
try { try {
mChannel.position(mOffset); mChannel.position(mOffset);
@ -161,7 +161,5 @@ public class ChunkFromFileChannelRequestEntity implements RequestEntity, Progres
} catch (FileRequestEntity.WriteException we) { } catch (FileRequestEntity.WriteException we) {
throw we.getWrapped(); throw we.getWrapped();
} }
} }
} }

View File

@ -0,0 +1,100 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2018 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 java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import okhttp3.MediaType;
import okio.BufferedSink;
public class ChunkFromFileRequestBody extends FileRequestBody {
private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName();
//private final File mFile;
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 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);
sink.write(mBuffer.array(), 0 ,readCount);
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());
}
}
}
sink.flush();
} catch (IOException io) {
// // any read problem will be handled as if the file is not there
// if (io instanceof FileNotFoundException) {
// throw io;
// } else {
// FileNotFoundException fnf = new FileNotFoundException("Exception reading source file");
// fnf.initCause(io);
// throw fnf;
// }
}
}
}

View File

@ -43,7 +43,7 @@ import okio.Source;
*/ */
public class FileRequestBody extends RequestBody implements ProgressiveDataTransferer { public class FileRequestBody extends RequestBody implements ProgressiveDataTransferer {
private File mFile; protected File mFile;
private MediaType mContentType; private MediaType mContentType;
Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>(); Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
@ -71,7 +71,7 @@ public class FileRequestBody extends RequestBody implements ProgressiveDataTrans
long transferred = 0; long transferred = 0;
long read; long read;
while ((read = source.read(sink.buffer(), 2048)) != -1) { while ((read = source.read(sink.buffer(), 4096)) != -1) {
transferred += read; transferred += read;
sink.flush(); sink.flush();
synchronized (mDataTransferListeners) { synchronized (mDataTransferListeners) {

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH. * Copyright (C) 2018 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -26,14 +26,28 @@ package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpUtils; import com.owncloud.android.lib.common.http.HttpUtils;
import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod; import com.owncloud.android.lib.common.http.methods.webdav.PutMethod;
import com.owncloud.android.lib.common.network.WebdavUtils; import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody;
import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import okhttp3.MediaType;
import static com.owncloud.android.lib.common.http.HttpConstants.IF_MATCH_HEADER;
import static com.owncloud.android.lib.common.http.HttpConstants.OC_TOTAL_LENGTH_HEADER;
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 class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation {
private static final int LAST_CHUNK_TIMEOUT = 900000; //15 mins. private static final int LAST_CHUNK_TIMEOUT = 900000; //15 mins.
@ -46,11 +60,6 @@ public class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation
private long mTransferId; private long mTransferId;
public ChunkedUploadRemoteFileOperation(String storagePath, String remotePath, String mimeType,
String fileLastModifTimestamp) {
super(storagePath, remotePath, mimeType, fileLastModifTimestamp);
}
public ChunkedUploadRemoteFileOperation(long transferId, String storagePath, String remotePath, String mimeType, public ChunkedUploadRemoteFileOperation(long transferId, String storagePath, String remotePath, String mimeType,
String requiredEtag, String fileLastModifTimestamp) { String requiredEtag, String fileLastModifTimestamp) {
super(storagePath, remotePath, mimeType, requiredEtag, fileLastModifTimestamp); super(storagePath, remotePath, mimeType, requiredEtag, fileLastModifTimestamp);
@ -65,80 +74,84 @@ public class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation
FileChannel channel = null; FileChannel channel = null;
RandomAccessFile raf = null; RandomAccessFile raf = null;
//TODO try {
// try { File fileToUpload = new File(mLocalPath);
// File file = new File(mLocalPath); MediaType mediaType = MediaType.parse(mMimeType);
// raf = new RandomAccessFile(file, "r");
// channel = raf.getChannel(); raf = new RandomAccessFile(fileToUpload, "r");
// mEntity = new ChunkFromFileChannelRequestEntity(channel, mMimeType, CHUNK_SIZE, file); channel = raf.getChannel();
// synchronized (mDataTransferListeners) {
// ((ProgressiveDataTransferer) mEntity) mFileRequestBody = new ChunkFromFileRequestBody(fileToUpload, mediaType, channel, CHUNK_SIZE);
// .addDatatransferProgressListeners(mDataTransferListeners);
// } synchronized (mDataTransferListeners) {
// mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners);
// long offset = 0; }
// String uriPrefix = client.getOldWebdavUri() + WebdavUtils.encodePath(mRemotePath) +
// "-chunking-" + Math.abs((new Random()).nextInt(9000) + 1000) + "-"; long offset = 0;
// long totalLength = file.length(); String uriPrefix = client.getNewUploadsWebDavUri() + FileUtils.PATH_SEPARATOR + String.valueOf(mTransferId);
// long chunkCount = (long) Math.ceil((double) totalLength / CHUNK_SIZE); long totalLength = fileToUpload.length();
// String chunkSizeStr = String.valueOf(CHUNK_SIZE); long chunkCount = (long) Math.ceil((double) totalLength / CHUNK_SIZE);
// String totalLengthStr = String.valueOf(file.length()); String chunkSizeStr = String.valueOf(CHUNK_SIZE);
// for (int chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++, offset += CHUNK_SIZE) { String totalLengthStr = String.valueOf(fileToUpload.length());
// if (chunkIndex == chunkCount - 1) {
// chunkSizeStr = String.valueOf(CHUNK_SIZE * chunkCount - totalLength); for (int chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++, offset += CHUNK_SIZE) {
// } if (chunkIndex == chunkCount - 1) {
// if (mPutMethod != null) { chunkSizeStr = String.valueOf(CHUNK_SIZE * chunkCount - totalLength);
// mPutMethod.releaseConnection(); // let the connection available }
// // for other methods
// } mPutMethod = new PutMethod(
// mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex); HttpUtils.stringUrlToHttpUrl(uriPrefix + FileUtils.PATH_SEPARATOR + chunkIndex)
// if (mRequiredEtag != null && mRequiredEtag.length() > 0) { );
// mPutMethod.addRequestHeader(IF_MATCH_HEADER, "\"" + mRequiredEtag + "\"");
// } if (mRequiredEtag != null && mRequiredEtag.length() > 0) {
mPutMethod.addRequestHeader(IF_MATCH_HEADER, "\"" + mRequiredEtag + "\"");
}
// mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER); // mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER);
// mPutMethod.addRequestHeader(OC_CHUNK_SIZE_HEADER, chunkSizeStr); // mPutMethod.addRequestHeader(OC_CHUNK_SIZE_HEADER, chunkSizeStr);
// mPutMethod.addRequestHeader(OC_TOTAL_LENGTH_HEADER, totalLengthStr); mPutMethod.addRequestHeader(OC_TOTAL_LENGTH_HEADER, totalLengthStr);
//
// mPutMethod.addRequestHeader(OC_CHUNK_X_OC_MTIME_HEADER, mFileLastModifTimestamp); mPutMethod.addRequestHeader(OC_CHUNK_X_OC_MTIME_HEADER, mFileLastModifTimestamp);
//
// ((ChunkFromFileChannelRequestEntity) mEntity).setOffset(offset); // ((ChunkFromFileChannelRequestEntity) mEntity).setOffset(offset);
// mPutMethod.setRequestEntity(mEntity); // mPutMethod.setRequestEntity(mEntity);
// if (mCancellationRequested.get()) { // if (mCancellationRequested.get()) {
// mPutMethod.abort(); // mPutMethod.abort();
// // next method will throw an exception // // next method will throw an exception
// } // }
//
// if (chunkIndex == chunkCount - 1) { // if (chunkIndex == chunkCount - 1) {
// // Added a high timeout to the last chunk due to when the last chunk // // 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 // // arrives to the server with the last PUT, all chunks get assembled
// // within that PHP request, so last one takes longer. // // within that PHP request, so last one takes longer.
// mPutMethod.getParams().setSoTimeout(LAST_CHUNK_TIMEOUT); // mPutMethod.getParams().setSoTimeout(LAST_CHUNK_TIMEOUT);
// } // }
//
// status = client.executeMethod(mPutMethod); mPutMethod.setRequestBody(mFileRequestBody);
//
// result = new RemoteOperationResult( status = client.executeHttpMethod(mPutMethod);
// isSuccess(status),
// mPutMethod Log_OC.d(TAG, "Upload of " + mLocalPath + " to " + mRemotePath +
// ); ", chunk index " + chunkIndex + ", count " + chunkCount +
// ", HTTP result status " + status);
// client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
// Log_OC.d(TAG, "Upload of " + mLocalPath + " to " + mRemotePath + if (isSuccess(status)) {
// ", chunk index " + chunkIndex + ", count " + chunkCount + result = new RemoteOperationResult(OK);
// ", HTTP result status " + status); } else {
// result = new RemoteOperationResult(mPutMethod);
// if (!isSuccess(status)) break;
// break; }
// } }
//
// } finally { } catch (Exception e) {
// if (channel != null) e.printStackTrace();
// channel.close(); } finally {
// if (raf != null) if (channel != null)
// raf.close(); channel.close();
if (raf != null)
raf.close();
// if (mPutMethod != null) // if (mPutMethod != null)
// mPutMethod.releaseConnection(); // let the connection available for other methods // mPutMethod.releaseConnection(); // let the connection available for other methods
// } }
return result; return result;
} }
} }