Skip to content

Add bandwidth limit to file download and upload functions #1667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import okio.Throttler;
import okio.Source;
import okio.Sink;
import okio.BufferedSink;
import okio.Okio;

/**
* A RequestEntity that represents a File.
*/
Expand All @@ -32,6 +35,7 @@ public class FileRequestEntity implements RequestEntity, ProgressiveDataTransfer
private final File file;
private final String contentType;
private final Set<OnDatatransferProgressListener> dataTransferListeners = new HashSet<>();
private final Throttler throttler = new Throttler();

public FileRequestEntity(final File file, final String contentType) {
super();
Expand Down Expand Up @@ -77,28 +81,45 @@ public void removeDataTransferProgressListener(OnDatatransferProgressListener li
dataTransferListeners.remove(listener);
}
}



/**
* @param limit Maximum upload speed in bytes per second.
* Disabled by default (limit 0).
*/
public void setBandwidthLimit(long limit) {
throttler.bytesPerSecond(limit);
}

@Override
public void writeRequest(final OutputStream out) throws IOException {
ByteBuffer tmp = ByteBuffer.allocate(4096);
int readResult;

RandomAccessFile raf = new RandomAccessFile(file, "r");
FileChannel channel = raf.getChannel();
long readResult;
Iterator<OnDatatransferProgressListener> it;
long transferred = 0;
long size = file.length();
if (size == 0) size = -1;

Source source = null;
Source bufferSource = null;
Sink sink = null;
Sink throttledSink = null;
BufferedSink bufferedThrottledSink = null;
try {
while ((readResult = channel.read(tmp)) >= 0) {
source = Okio.source(file);
bufferSource = Okio.buffer(source);

sink = Okio.sink(out);
throttledSink = throttler.sink(sink);
bufferedThrottledSink = Okio.buffer(throttledSink);

while ((readResult = bufferSource.read(bufferedThrottledSink.getBuffer(), 4096)) >= 0) {
try {
out.write(tmp.array(), 0, readResult);
bufferedThrottledSink.emitCompleteSegments();

} catch (IOException io) {
// work-around try catch to filter exception in writing
throw new WriteException(io);
}
tmp.clear();

transferred += readResult;
synchronized (dataTransferListeners) {
it = dataTransferListeners.iterator();
Expand All @@ -107,6 +128,7 @@ public void writeRequest(final OutputStream out) throws IOException {
}
}
}
bufferedThrottledSink.flush();

} catch (IOException io) {
// any read problem will be handled as if the file is not there
Expand All @@ -123,8 +145,12 @@ public void writeRequest(final OutputStream out) throws IOException {

} finally {
try {
channel.close();
raf.close();
// TODO Which of these are even necessary? (Been a while since I last dealt with buffers)
if (source != null) source.close();
if (bufferSource != null) bufferSource.close();
// if (sink != null) sink.close();
// if (throttledSink != null) throttledSink.close();
// if (bufferedThrottledSink != null) bufferedThrottledSink.close();
} catch (IOException io) {
// ignore failures closing source file
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;

import java.io.BufferedInputStream;
import okio.Throttler;
import okio.Source;
import okio.BufferedSink;
import okio.Okio;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
Expand All @@ -44,6 +47,7 @@ public class DownloadFileRemoteOperation extends RemoteOperation {
private long modificationTimestamp = 0;
private String eTag = "";
private GetMethod getMethod;
private final Throttler throttler = new Throttler();

private String remotePath;
private String temporalFolderPath;
Expand All @@ -57,8 +61,16 @@ public DownloadFileRemoteOperation(String remotePath, String temporalFolderPath)
this.temporalFolderPath = temporalFolderPath;
}

@Override
protected RemoteOperationResult run(OwnCloudClient client) {
/**
* @param limit Maximum download speed in bytes per second.
* Disabled by default (limit 0).
*/
public void setBandwidthLimit(long limit) {
throttler.bytesPerSecond(limit);
}

@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;

/// download will be performed to a temporal file, then moved to the final location
Expand Down Expand Up @@ -88,7 +100,11 @@ private int downloadFile(OwnCloudClient client, File targetFile) throws IOExcept
getMethod = new GetMethod(client.getFilesDavUri(remotePath));
Iterator<OnDatatransferProgressListener> it;

FileOutputStream fos = null;
// TODO If the upload and download limits should be global then the same throttler can be used for
// all instances of this and the upload class.
Source bufferSource = null;
Source throttledBufferSource = null;
BufferedSink bufferSink = null;
try {
status = client.executeMethod(getMethod);
if (isSuccess(status)) {
Expand All @@ -98,25 +114,26 @@ private int downloadFile(OwnCloudClient client, File targetFile) throws IOExcept
Log_OC.e(TAG, "Error creating file " + targetFile.getAbsolutePath(), ex);
throw new CreateLocalFileException(targetFile.getPath(), ex);
}
BufferedInputStream bis = new BufferedInputStream(getMethod.getResponseBodyAsStream());
fos = new FileOutputStream(targetFile);
bufferSource = Okio.source(getMethod.getResponseBodyAsStream());
throttledBufferSource = throttler.source(bufferSource);
bufferSink = Okio.buffer(Okio.sink(targetFile));

long transferred = 0;

Header contentLength = getMethod.getResponseHeader("Content-Length");
long totalToTransfer = (contentLength != null &&
contentLength.getValue().length() > 0) ?
Long.parseLong(contentLength.getValue()) : 0;
contentLength.getValue().length() > 0) ?
Long.parseLong(contentLength.getValue()) : 0;

byte[] bytes = new byte[4096];
int readResult;
while ((readResult = bis.read(bytes)) != -1) {
long readResult;
while ((readResult = throttledBufferSource.read(bufferSink.getBuffer(), 4096)) != -1) {
bufferSink.emitCompleteSegments();
synchronized (mCancellationRequested) {
if (mCancellationRequested.get()) {
getMethod.abort();
throw new OperationCancelledException();
}
}
fos.write(bytes, 0, readResult);
transferred += readResult;
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
Expand All @@ -126,6 +143,7 @@ private int downloadFile(OwnCloudClient client, File targetFile) throws IOExcept
}
}
}
bufferSink.flush();
// Check if the file is completed
// if transfer-encoding: chunked we cannot check if the file is complete
Header transferEncodingHeader = getMethod.getResponseHeader("Transfer-Encoding");
Expand Down Expand Up @@ -163,7 +181,11 @@ private int downloadFile(OwnCloudClient client, File targetFile) throws IOExcept
}

} finally {
if (fos != null) fos.close();
// TODO Any of these need try statements? Which of these are even necessary? (Been a while since I last dealt with buffers)
if (bufferSource != null) bufferSource.close();
if (throttledBufferSource != null) throttledBufferSource.close();
if (bufferSink != null) bufferSink.close();

if (!savedFile && targetFile.exists()) {
targetFile.delete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class UploadFileRemoteOperation extends RemoteOperation<String> {
final Set<OnDatatransferProgressListener> dataTransferListeners = new HashSet<>();

protected RequestEntity entity = null;
private long bandwidthLimit = 0;

@VisibleForTesting
public UploadFileRemoteOperation() {
Expand Down Expand Up @@ -134,6 +135,20 @@ public UploadFileRemoteOperation(String localPath,
this.creationTimestamp = creationTimestamp;
}

/**
* @param limit Maximum upload speed in bytes per second.
* Disabled by default (limit 0).
*/
public void setBandwidthLimit(long limit) {
bandwidthLimit = limit;

// If already in progress then set the limit immediately
// Otherwise it will be saved and set when it's run.
if (entity != null) {
((FileRequestEntity) entity).setBandwidthLimit(limit);
}
}

@Override
protected RemoteOperationResult<String> run(OwnCloudClient client) {
RemoteOperationResult<String> result;
Expand Down Expand Up @@ -193,6 +208,7 @@ protected RemoteOperationResult<String> uploadFile(OwnCloudClient client) throws
try {
File f = new File(localPath);
entity = new FileRequestEntity(f, mimeType);
((FileRequestEntity) entity).setBandwidthLimit(bandwidthLimit);
synchronized (dataTransferListeners) {
((ProgressiveDataTransfer) entity)
.addDataTransferProgressListeners(dataTransferListeners);
Expand Down
Loading
Loading