Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
fr.bmartel.speedtest.SpeedTestTask Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2017 Bertrand Martel
*
* 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 fr.bmartel.speedtest;
import fr.bmartel.protocol.http.HttpFrame;
import fr.bmartel.protocol.http.states.HttpStates;
import fr.bmartel.speedtest.inter.ISpeedTestListener;
import fr.bmartel.speedtest.inter.ISpeedTestSocket;
import fr.bmartel.speedtest.model.FtpMode;
import fr.bmartel.speedtest.model.SpeedTestError;
import fr.bmartel.speedtest.model.SpeedTestMode;
import fr.bmartel.speedtest.model.UploadStorageType;
import fr.bmartel.speedtest.utils.RandomGen;
import fr.bmartel.speedtest.utils.SpeedTestUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.*;
import java.util.List;
import java.util.concurrent.*;
/**
* This class manage all download/upload operations.
*
* @author Bertrand Martel
*/
public class SpeedTestTask {
/**
* socket server hostname.
*/
private String mHostname = "";
/**
* socket server port.
*/
private int mPort;
/**
* Protocol used (http/https/ftp...).
*/
private String mProtocol;
/**
* proxy URL.
*/
private URL mProxyUrl;
/**
* socket object.
*/
private Socket mSocket;
/**
* start time triggered in millis.
*/
private long mTimeStart;
/**
* start time for the current transfer rate computation.
*/
private long mTimeComputeStart;
/**
* end time triggered in millis.
*/
private long mTimeEnd;
/**
* this is the number of bit uploaded at this time.
*/
private int mUploadTempFileSize;
/**
* number of bit uploaded since last transfer rate computation.
*/
private int mUlComputationTempFileSize;
/**
* this is the number of packet downloaded at this time.
*/
private int mDownloadTemporaryPacketSize;
/**
* number of packet download since the last computation.
*/
private int mDlComputationTempPacketSize;
/**
* this is the number of packet to download.
*/
private BigDecimal mDownloadPckSize = BigDecimal.ZERO;
/**
* FTP inputstream.
*/
private InputStream mFtpInputstream;
/**
* FTP outputstream.
*/
private OutputStream mFtpOutputstream;
/**
* define if an error has been dispatched already or not. This is reset to false on start download/ upload + in
* reading thread
*/
private boolean mErrorDispatched;
/**
* define if mSocket close error is to be expected.
*/
private boolean mForceCloseSocket;
/**
* size of file to upload.
*/
private BigDecimal mUploadFileSize = BigDecimal.ZERO;
/**
* SpeedTestSocket interface.
*/
private final ISpeedTestSocket mSocketInterface;
/**
* Speed test repeat wrapper.
*/
private final RepeatWrapper mRepeatWrapper;
/**
* Listener list.
*/
private final List mListenerList;
/**
* define if report interval is set.
*/
private boolean mReportInterval;
/**
* executor service for reading operation.
*/
private ExecutorService mReadExecutorService;
/**
* executor service for writing operation.
*/
private ExecutorService mWriteExecutorService;
/**
* executor service used for reporting.
*/
private ScheduledExecutorService mReportExecutorService;
/**
* current speed test mode.
*/
private SpeedTestMode mSpeedTestMode = SpeedTestMode.NONE;
/**
* Build socket.
*
* @param socketInterface interface shared between repeat wrapper and speed test socket
*/
public SpeedTestTask(final ISpeedTestSocket socketInterface, final List listenerList) {
mSocketInterface = socketInterface;
mRepeatWrapper = mSocketInterface.getRepeatWrapper();
mListenerList = listenerList;
initThreadPool();
}
/**
* initialize thread pool.
*/
private void initThreadPool() {
mReadExecutorService = Executors.newSingleThreadExecutor();
mReportExecutorService = Executors.newScheduledThreadPool(SpeedTestConst.THREAD_POOL_REPORT_SIZE);
mWriteExecutorService = Executors.newSingleThreadExecutor();
}
/**
* Set report interval state.
*
* @param state define if a report interval is set
*/
public void setReportInterval(final boolean state) {
mReportInterval = state;
}
/**
* Set proxy URI.
*
* @param proxyUri proxy URI
* @return false if malformed
*/
public boolean setProxy(final String proxyUri) {
try {
mProxyUrl = (proxyUri != null) ? new URL(proxyUri) : null;
} catch (MalformedURLException e) {
return false;
}
return true;
}
/**
* start download task.
*
* @param uri uri to fetch to download file
*/
public void startDownloadRequest(final String uri) {
mSpeedTestMode = SpeedTestMode.DOWNLOAD;
mForceCloseSocket = false;
mErrorDispatched = false;
try {
final URL url = new URL(uri);
mProtocol = url.getProtocol();
switch (mProtocol) {
case "http":
case "https":
String downloadRequest;
if (mProxyUrl != null) {
this.mHostname = mProxyUrl.getHost();
this.mPort = mProxyUrl.getPort() != -1 ? mProxyUrl.getPort() : 8080;
downloadRequest = "GET " + uri + " HTTP/1.1\r\n" + "Host: " + url.getHost() +
"\r\nProxy-Connection: Keep-Alive" + "\r\n\r\n";
} else {
this.mHostname = url.getHost();
if (url.getProtocol().equals("http")) {
this.mPort = url.getPort() != -1 ? url.getPort() : 80;
} else {
this.mPort = url.getPort() != -1 ? url.getPort() : 443;
}
downloadRequest = "GET " + uri + " HTTP/1.1\r\n" + "Host: " + url.getHost() + "\r\n\r\n";
}
writeDownload(downloadRequest.getBytes());
break;
case "ftp":
final String userInfo = url.getUserInfo();
String user = SpeedTestConst.FTP_DEFAULT_USER;
String pwd = SpeedTestConst.FTP_DEFAULT_PASSWORD;
if (userInfo != null && userInfo.indexOf(':') != -1) {
user = userInfo.substring(0, userInfo.indexOf(':'));
pwd = userInfo.substring(userInfo.indexOf(':') + 1);
}
startFtpDownload(uri, user, pwd);
break;
default:
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.UNSUPPORTED_PROTOCOL,
"unsupported protocol");
break;
}
} catch (MalformedURLException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.MALFORMED_URI,
e.getMessage());
}
}
/**
* Start upload request, distinguish protocol.
*
* @param uri URI
* @param fileSizeOctet file size to upload in octet
*/
public void startUploadRequest(final String uri, final int fileSizeOctet) {
mSpeedTestMode = SpeedTestMode.UPLOAD;
mForceCloseSocket = false;
mErrorDispatched = false;
try {
final URL url = new URL(uri);
switch (url.getProtocol()) {
case "http":
case "https":
writeUpload(uri, fileSizeOctet);
break;
case "ftp":
startFtpUpload(uri, fileSizeOctet);
break;
default:
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.UNSUPPORTED_PROTOCOL,
"unsupported protocol");
break;
}
} catch (MalformedURLException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.MALFORMED_URI,
e.getMessage());
}
}
/**
* shutdown executors to release threads.
*/
private void closeExecutors() {
mReadExecutorService.shutdownNow();
mReportExecutorService.shutdownNow();
mWriteExecutorService.shutdownNow();
}
/**
* Write upload POST request with file generated randomly.
*
* @param uri URI
* @param fileSizeOctet file size to upload in octet
*/
public void writeUpload(final String uri, final int fileSizeOctet) {
try {
final URL url = new URL(uri);
mProtocol = url.getProtocol();
if (mProxyUrl != null) {
this.mHostname = mProxyUrl.getHost();
this.mPort = mProxyUrl.getPort() != -1 ? mProxyUrl.getPort() : 8080;
} else {
this.mHostname = url.getHost();
if ("http".equals(mProtocol)) {
this.mPort = url.getPort() != -1 ? url.getPort() : 80;
} else {
this.mPort = url.getPort() != -1 ? url.getPort() : 443;
}
}
mUploadFileSize = new BigDecimal(fileSizeOctet);
mUploadTempFileSize = 0;
mUlComputationTempFileSize = 0;
mTimeStart = System.nanoTime();
mTimeComputeStart = System.nanoTime();
connectAndExecuteTask(new Runnable() {
@Override
public void run() {
if (mSocket != null && !mSocket.isClosed()) {
RandomAccessFile uploadFile = null;
final RandomGen randomGen = new RandomGen();
try {
byte[] body = new byte[]{};
if (mSocketInterface.getUploadStorageType() == UploadStorageType.RAM_STORAGE) {
/* generate a file with size of fileSizeOctet octet */
body = randomGen.generateRandomArray(fileSizeOctet);
} else {
uploadFile = randomGen.generateRandomFile(fileSizeOctet);
uploadFile.seek(0);
}
String head;
if (mProxyUrl != null) {
head = "POST " + uri + " HTTP/1.1\r\n" + "Host: " + url.getHost() +
"\r\nAccept: " + "*/*\r\nContent-Length: " + fileSizeOctet +
"\r\nProxy-Connection: Keep-Alive" + "\r\n\r\n";
} else {
head = "POST " + uri + " HTTP/1.1\r\n" + "Host: " + url.getHost() +
"\r\nAccept: " + "*/*\r\nContent-Length: " + fileSizeOctet + "\r\n\r\n";
}
mUploadTempFileSize = 0;
mUlComputationTempFileSize = 0;
final int uploadChunkSize = mSocketInterface.getUploadChunkSize();
final int step = fileSizeOctet / uploadChunkSize;
final int remain = fileSizeOctet % uploadChunkSize;
if (mSocket.getOutputStream() != null) {
if (writeFlushSocket(head.getBytes()) != 0) {
throw new SocketTimeoutException();
}
mTimeStart = System.nanoTime();
mTimeComputeStart = System.nanoTime();
mTimeEnd = 0;
if (mRepeatWrapper.isFirstUpload()) {
mRepeatWrapper.setFirstUploadRepeat(false);
mRepeatWrapper.setStartDate(mTimeStart);
}
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updatePacketSize(mUploadFileSize);
}
for (int i = 0; i < step; i++) {
final byte[] chunk = SpeedTestUtils.readUploadData(mSocketInterface
.getUploadStorageType(),
body,
uploadFile,
mUploadTempFileSize,
uploadChunkSize);
if (writeFlushSocket(chunk) != 0) {
throw new SocketTimeoutException();
}
mUploadTempFileSize += uploadChunkSize;
mUlComputationTempFileSize += uploadChunkSize;
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updateTempPacketSize(uploadChunkSize);
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int j = 0; j < mListenerList.size(); j++) {
mListenerList.get(j).onProgress(report.getProgressPercent(), report);
}
}
}
final byte[] chunk = SpeedTestUtils.readUploadData(mSocketInterface
.getUploadStorageType(),
body,
uploadFile,
mUploadTempFileSize,
remain);
if (remain != 0 && writeFlushSocket(chunk) != 0) {
throw new SocketTimeoutException();
} else {
mUploadTempFileSize += remain;
mUlComputationTempFileSize += remain;
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updateTempPacketSize(remain);
}
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int j = 0; j < mListenerList.size(); j++) {
mListenerList.get(j).onProgress(SpeedTestConst.PERCENT_MAX.floatValue(),
report);
}
}
}
} catch (SocketTimeoutException e) {
mReportInterval = false;
mErrorDispatched = true;
closeSocket();
closeExecutors();
if (!mForceCloseSocket) {
SpeedTestUtils.dispatchSocketTimeout(mForceCloseSocket, mListenerList, SpeedTestConst
.SOCKET_WRITE_ERROR);
} else {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
e.getMessage());
}
} catch (IOException e) {
mReportInterval = false;
mErrorDispatched = true;
closeExecutors();
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, e.getMessage());
} finally {
if (uploadFile != null) {
try {
uploadFile.close();
randomGen.deleteFile();
} catch (IOException e) {
//e.printStackTrace();
}
}
}
}
}
}, false, fileSizeOctet);
} catch (MalformedURLException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.MALFORMED_URI,
e.getMessage());
}
}
/**
* Create and connect mSocket.
*
* @param task task to be executed when connected to mSocket
* @param download define if it is a download or upload test
* @param uploadSize upload package size (if !download)
*/
private void connectAndExecuteTask(final Runnable task, final boolean download, final int uploadSize) {
// close mSocket before recreating it
if (mSocket != null) {
closeSocket();
}
try {
if ("https".equals(mProtocol)) {
final SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory.getDefault();
mSocket = ssf.createSocket();
} else {
mSocket = new Socket();
}
if (mSocketInterface.getSocketTimeout() != 0 && download) {
mSocket.setSoTimeout(mSocketInterface.getSocketTimeout());
}
/* establish mSocket parameters */
mSocket.setReuseAddress(true);
mSocket.setKeepAlive(true);
mSocket.connect(new InetSocketAddress(mHostname, mPort));
if (mReadExecutorService == null || mReadExecutorService.isShutdown()) {
mReadExecutorService = Executors.newSingleThreadExecutor();
}
mReadExecutorService.execute(new Runnable() {
@Override
public void run() {
if (download) {
startSocketDownloadTask(mProtocol, mHostname);
} else {
startSocketUploadTask(mHostname, uploadSize);
}
}
});
if (mWriteExecutorService == null || mWriteExecutorService.isShutdown()) {
mWriteExecutorService = Executors.newSingleThreadExecutor();
}
mWriteExecutorService.execute(new Runnable() {
@Override
public void run() {
if (task != null) {
task.run();
}
}
});
} catch (IOException e) {
if (!mErrorDispatched) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList, e.getMessage());
}
}
}
/**
* start download reading task.
*
* @String hostname hostname to reach
*/
private void startSocketDownloadTask(final String protocol, final String hostname) {
mDownloadTemporaryPacketSize = 0;
mDlComputationTempPacketSize = 0;
try {
final HttpFrame httpFrame = new HttpFrame();
final HttpStates httFrameState = httpFrame.decodeFrame(mSocket.getInputStream());
SpeedTestUtils.checkHttpFrameError(mForceCloseSocket, mListenerList, httFrameState);
final HttpStates httpHeaderState = httpFrame.parseHeader(mSocket.getInputStream());
SpeedTestUtils.checkHttpHeaderError(mForceCloseSocket, mListenerList, httpHeaderState);
if (httpFrame.getStatusCode() == SpeedTestConst.HTTP_OK &&
httpFrame.getReasonPhrase().equalsIgnoreCase("ok")) {
SpeedTestUtils.checkHttpContentLengthError(mForceCloseSocket,
mListenerList, httpFrame);
mDownloadPckSize = new BigDecimal(httpFrame.getContentLength());
if (mRepeatWrapper.isRepeatDownload()) {
mRepeatWrapper.updatePacketSize(mDownloadPckSize);
}
mTimeStart = System.nanoTime();
mTimeComputeStart = System.nanoTime();
mTimeEnd = 0;
if (mRepeatWrapper.isFirstDownload()) {
mRepeatWrapper.setFirstDownloadRepeat(false);
mRepeatWrapper.setStartDate(mTimeStart);
}
downloadReadingLoop();
mTimeEnd = System.nanoTime();
closeSocket();
mReportInterval = false;
if (!mRepeatWrapper.isRepeatDownload()) {
closeExecutors();
}
final SpeedTestReport report = getReport(SpeedTestMode.DOWNLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onCompletion(report);
}
} else if ((httpFrame.getStatusCode() == 301 ||
httpFrame.getStatusCode() == 302 ||
httpFrame.getStatusCode() == 307) &&
httpFrame.getHeaders().containsKey("location")) {
// redirect to Location
final String location = httpFrame.getHeaders().get("location");
if (location.charAt(0) == '/') {
mReportInterval = false;
finishTask();
startDownloadRequest(protocol + "://" + hostname + location);
} else {
mReportInterval = false;
finishTask();
startDownloadRequest(location);
}
} else {
mReportInterval = false;
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onError(SpeedTestError.INVALID_HTTP_RESPONSE, "Error status code " +
httpFrame.getStatusCode());
}
finishTask();
}
} catch (
SocketTimeoutException e
)
{
mReportInterval = false;
SpeedTestUtils.dispatchSocketTimeout(mForceCloseSocket, mListenerList, e.getMessage());
mTimeEnd = System.nanoTime();
closeSocket();
closeExecutors();
} catch (IOException |
InterruptedException e
)
{
mReportInterval = false;
catchError(e.getMessage());
}
mErrorDispatched = false;
}
private void finishTask() {
closeSocket();
if (!mRepeatWrapper.isRepeatDownload()) {
closeExecutors();
}
}
/**
* start download reading loop + monitor progress.
*
* @throws IOException mSocket io exception
*/
private void downloadReadingLoop() throws IOException {
final byte[] buffer = new byte[SpeedTestConst.READ_BUFFER_SIZE];
int read;
while ((read = mSocket.getInputStream().read(buffer)) != -1) {
mDownloadTemporaryPacketSize += read;
mDlComputationTempPacketSize += read;
if (mRepeatWrapper.isRepeatDownload()) {
mRepeatWrapper.updateTempPacketSize(read);
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.DOWNLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onProgress(report.getProgressPercent(), report);
}
}
if (mDownloadTemporaryPacketSize == mDownloadPckSize.longValueExact()) {
break;
}
}
}
/**
* start upload writing task.
*
* @param hostname hostname to reach
* @param size upload packet size
*/
private void startSocketUploadTask(final String hostname, final int size) {
try {
final HttpFrame frame = new HttpFrame();
final HttpStates httpStates = frame.parseHttp(mSocket.getInputStream());
if (httpStates == HttpStates.HTTP_FRAME_OK) {
if (frame.getStatusCode() == SpeedTestConst.HTTP_OK && frame.getReasonPhrase().equalsIgnoreCase("ok")) {
mTimeEnd = System.nanoTime();
mReportInterval = false;
finishTask();
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onCompletion(report);
}
} else if ((frame.getStatusCode() == 301 ||
frame.getStatusCode() == 302 ||
frame.getStatusCode() == 307) &&
frame.getHeaders().containsKey("location")) {
// redirect to Location
final String location = frame.getHeaders().get("location");
if (location.charAt(0) == '/') {
mReportInterval = false;
finishTask();
startUploadRequest("http://" + hostname + location, size);
} else if (location.startsWith("https")) {
//unsupported protocol
mReportInterval = false;
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onError(SpeedTestError.UNSUPPORTED_PROTOCOL, "unsupported protocol :" +
" " +
"https");
}
finishTask();
} else {
mReportInterval = false;
finishTask();
startUploadRequest(location, size);
}
} else {
mReportInterval = false;
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onError(SpeedTestError.INVALID_HTTP_RESPONSE, "Error status code" +
" " + frame.getStatusCode());
}
finishTask();
}
return;
}
closeSocket();
if (!mErrorDispatched && !mForceCloseSocket) {
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onError(SpeedTestError.SOCKET_ERROR, "mSocket error");
}
}
closeExecutors();
} catch (IOException | InterruptedException e) {
mReportInterval = false;
if (!mErrorDispatched) {
catchError(e.getMessage());
}
}
mErrorDispatched = false;
}
/**
* Write download request to server host.
*
* @param data HTTP request to send to initiate download process
*/
private void writeDownload(final byte[] data) {
connectAndExecuteTask(new Runnable() {
@Override
public void run() {
if (mSocket != null && !mSocket.isClosed()) {
try {
if ((mSocket.getOutputStream() != null) && (writeFlushSocket(data) != 0)) {
throw new SocketTimeoutException();
}
} catch (SocketTimeoutException e) {
SpeedTestUtils.dispatchSocketTimeout(mForceCloseSocket, mListenerList, SpeedTestConst
.SOCKET_WRITE_ERROR);
closeSocket();
closeExecutors();
} catch (IOException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, e.getMessage());
closeExecutors();
}
}
}
}, true, 0);
}
/**
* logout & disconnect FTP client.
*
* @param ftpclient ftp client
*/
private void disconnectFtp(final FTPClient ftpclient) {
try {
if (ftpclient.isConnected()) {
ftpclient.logout();
ftpclient.disconnect();
}
} catch (IOException ex) {
//ex.printStackTrace();
}
}
/**
* write and flush mSocket.
*
* @param data payload to write
* @return error status (-1 for error)
* @throws IOException mSocket io exception
*/
private int writeFlushSocket(final byte[] data) throws IOException {
final ExecutorService executor = Executors.newSingleThreadExecutor();
@SuppressWarnings("unchecked") final Future future = executor.submit(new Callable() {
/**
* execute sequential write/flush task.
*
* @return status
*/
public Integer call() {
try {
mSocket.getOutputStream().write(data);
mSocket.getOutputStream().flush();
} catch (IOException e) {
return -1;
}
return 0;
}
});
int status;
try {
status = future.get(mSocketInterface.getSocketTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
status = -1;
} catch (InterruptedException | ExecutionException e) {
status = -1;
}
executor.shutdownNow();
return status;
}
/**
* catch an error.
*
* @param errorMessage error message from Exception
*/
private void catchError(final String errorMessage) {
mTimeEnd = System.nanoTime();
closeSocket();
closeExecutors();
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList, errorMessage);
}
/**
* get a download/upload report.
*
* @param mode speed test mode requested
* @return speed test report
*/
public SpeedTestReport getReport(final SpeedTestMode mode) {
BigDecimal temporaryPacketSize = BigDecimal.ZERO;
BigDecimal totalPacketSize = BigDecimal.ZERO;
switch (mode) {
case DOWNLOAD:
temporaryPacketSize = new BigDecimal(mDownloadTemporaryPacketSize);
totalPacketSize = mDownloadPckSize;
break;
case UPLOAD:
temporaryPacketSize = new BigDecimal(mUploadTempFileSize);
totalPacketSize = mUploadFileSize;
break;
default:
break;
}
long currentTime;
if (mTimeEnd == 0) {
currentTime = System.nanoTime();
} else {
currentTime = mTimeEnd;
}
BigDecimal transferRateOps = BigDecimal.ZERO;
final int scale = mSocketInterface.getDefaultScale();
final RoundingMode roundingMode = mSocketInterface.getDefaultRoundingMode();
switch (mSocketInterface.getComputationMethod()) {
case MEDIAN_ALL_TIME:
BigDecimal dividerAllTime = new BigDecimal(currentTime - mTimeComputeStart)
.divide(SpeedTestConst.NANO_DIVIDER, scale, roundingMode);
if (shallCalculateTransferRate(currentTime) && dividerAllTime.compareTo(BigDecimal.ZERO) != 0) {
transferRateOps = temporaryPacketSize.divide(dividerAllTime, scale, roundingMode);
}
break;
case MEDIAN_INTERVAL:
final BigDecimal tempPacket = (mode == SpeedTestMode.DOWNLOAD) ? new BigDecimal
(mDlComputationTempPacketSize) : new BigDecimal(mUlComputationTempFileSize);
BigDecimal dividerMedian = new BigDecimal(currentTime - mTimeComputeStart)
.divide(SpeedTestConst.NANO_DIVIDER, scale, roundingMode);
if (shallCalculateTransferRate(currentTime) && dividerMedian.compareTo(BigDecimal.ZERO) != 0) {
transferRateOps = tempPacket.divide(dividerMedian, scale, roundingMode);
}
// reset those values for the next computation
mDlComputationTempPacketSize = 0;
mUlComputationTempFileSize = 0;
mTimeComputeStart = System.nanoTime();
break;
default:
break;
}
final BigDecimal transferRateBitps = transferRateOps.multiply(SpeedTestConst.BIT_MULTIPLIER);
BigDecimal percent = BigDecimal.ZERO;
SpeedTestReport report;
if (mRepeatWrapper.isRepeat()) {
report = mRepeatWrapper.getRepeatReport(scale, roundingMode, mode, currentTime, transferRateOps);
} else {
if (totalPacketSize.compareTo(BigDecimal.ZERO) != 0) {
percent = temporaryPacketSize.multiply(SpeedTestConst.PERCENT_MAX).divide(totalPacketSize, scale,
roundingMode);
}
report = new SpeedTestReport(mode, percent.floatValue(),
mTimeStart, currentTime, temporaryPacketSize.longValueExact(), totalPacketSize.longValueExact(),
transferRateOps, transferRateBitps,
1);
}
return report;
}
/**
* Check setup time depending on elapsed time.
*
* @param currentTime elapsed time since upload/download has started
* @return status if transfer rate should be computed at this time
*/
private boolean shallCalculateTransferRate(final long currentTime) {
final long elapsedTime = currentTime - mTimeStart;
boolean ret = true;
switch (mSpeedTestMode) {
case DOWNLOAD:
ret = (elapsedTime > mSocketInterface.getDownloadSetupTime());
break;
case UPLOAD:
ret = (elapsedTime > mSocketInterface.getUploadSetupTime());
break;
default:
}
return ret;
}
/**
* Get FTP file size.
*
* @param ftpClient ftp client
* @param filePath remote file path
* @return file size
* @throws Exception file read/write IOException
*/
private long getFileSize(final FTPClient ftpClient, final String filePath) throws IOException {
long fileSize = 0;
final FTPFile[] files = ftpClient.listFiles(filePath);
if (files.length == 1 && files[0].isFile()) {
fileSize = files[0].getSize();
}
return fileSize;
}
/**
* start FTP download with specific port, user, password.
*
* @param uri ftp uri
* @param user ftp username
* @param password ftp password
*/
public void startFtpDownload(
final String uri,
final String user,
final String password) {
mSpeedTestMode = SpeedTestMode.DOWNLOAD;
try {
final URL url = new URL(uri);
mErrorDispatched = false;
mForceCloseSocket = false;
if (mReadExecutorService == null || mReadExecutorService.isShutdown()) {
mReadExecutorService = Executors.newSingleThreadExecutor();
}
mReadExecutorService.execute(new Runnable() {
@Override
public void run() {
final FTPClient ftpclient = new FTPClient();
try {
ftpclient.connect(url.getHost(), url.getPort() != -1 ? url.getPort() : 21);
ftpclient.login(user, password);
if (mSocketInterface.getFtpMode() == FtpMode.PASSIVE) {
ftpclient.enterLocalPassiveMode();
} else {
ftpclient.enterLocalActiveMode();
}
ftpclient.setFileType(FTP.BINARY_FILE_TYPE);
mDownloadTemporaryPacketSize = 0;
mDlComputationTempPacketSize = 0;
mTimeStart = System.nanoTime();
mTimeComputeStart = System.nanoTime();
mTimeEnd = 0;
if (mRepeatWrapper.isFirstDownload()) {
mRepeatWrapper.setFirstDownloadRepeat(false);
mRepeatWrapper.setStartDate(mTimeStart);
}
mDownloadPckSize = new BigDecimal(getFileSize(ftpclient, url.getPath()));
if (mRepeatWrapper.isRepeatDownload()) {
mRepeatWrapper.updatePacketSize(mDownloadPckSize);
}
mFtpInputstream = ftpclient.retrieveFileStream(url.getPath());
if (mFtpInputstream != null) {
final byte[] bytesArray = new byte[SpeedTestConst.READ_BUFFER_SIZE];
int read;
while ((read = mFtpInputstream.read(bytesArray)) != -1) {
mDownloadTemporaryPacketSize += read;
mDlComputationTempPacketSize += read;
if (mRepeatWrapper.isRepeatDownload()) {
mRepeatWrapper.updateTempPacketSize(read);
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.DOWNLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onProgress(report.getProgressPercent(), report);
}
}
if (mDownloadTemporaryPacketSize == mDownloadPckSize.longValueExact()) {
break;
}
}
mFtpInputstream.close();
mTimeEnd = System.nanoTime();
mReportInterval = false;
final SpeedTestReport report = getReport(SpeedTestMode.DOWNLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onCompletion(report);
}
} else {
mReportInterval = false;
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, "cant create stream " +
"from uri " + uri + " with reply code : " + ftpclient.getReplyCode());
}
if (!mRepeatWrapper.isRepeatDownload()) {
closeExecutors();
}
} catch (IOException e) {
//e.printStackTrace();
mReportInterval = false;
catchError(e.getMessage());
} finally {
mErrorDispatched = false;
disconnectFtp(ftpclient);
}
}
});
} catch (MalformedURLException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.MALFORMED_URI,
e.getMessage());
}
}
/**
* Start FTP upload.
*
* @param uri upload uri
* @param fileSizeOctet file size in octet
*/
public void startFtpUpload(
final String uri,
final int fileSizeOctet) {
mSpeedTestMode = SpeedTestMode.UPLOAD;
mUploadFileSize = new BigDecimal(fileSizeOctet);
mForceCloseSocket = false;
mErrorDispatched = false;
try {
final URL url = new URL(uri);
final String userInfo = url.getUserInfo();
String user = SpeedTestConst.FTP_DEFAULT_USER;
String pwd = SpeedTestConst.FTP_DEFAULT_PASSWORD;
if (userInfo != null && userInfo.indexOf(':') != -1) {
user = userInfo.substring(0, userInfo.indexOf(':'));
pwd = userInfo.substring(userInfo.indexOf(':') + 1);
}
if (mWriteExecutorService == null || mWriteExecutorService.isShutdown()) {
mWriteExecutorService = Executors.newSingleThreadExecutor();
}
final String finalUser = user;
final String finalPwd = pwd;
mWriteExecutorService.execute(new Runnable() {
@Override
public void run() {
final FTPClient ftpClient = new FTPClient();
final RandomGen randomGen = new RandomGen();
RandomAccessFile uploadFile = null;
try {
ftpClient.connect(url.getHost(), url.getPort() != -1 ? url.getPort() : 21);
ftpClient.login(finalUser, finalPwd);
if (mSocketInterface.getFtpMode() == FtpMode.PASSIVE) {
ftpClient.enterLocalPassiveMode();
} else {
ftpClient.enterLocalActiveMode();
}
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
byte[] fileContent = new byte[]{};
if (mSocketInterface.getUploadStorageType() == UploadStorageType.RAM_STORAGE) {
/* generate a file with size of fileSizeOctet octet */
fileContent = randomGen.generateRandomArray(fileSizeOctet);
} else {
uploadFile = randomGen.generateRandomFile(fileSizeOctet);
uploadFile.seek(0);
}
mFtpOutputstream = ftpClient.storeFileStream(url.getPath());
if (mFtpOutputstream != null) {
mUploadTempFileSize = 0;
mUlComputationTempFileSize = 0;
final int uploadChunkSize = mSocketInterface.getUploadChunkSize();
final int step = fileSizeOctet / uploadChunkSize;
final int remain = fileSizeOctet % uploadChunkSize;
mTimeStart = System.nanoTime();
mTimeComputeStart = System.nanoTime();
mTimeEnd = 0;
if (mRepeatWrapper.isFirstUpload()) {
mRepeatWrapper.setFirstUploadRepeat(false);
mRepeatWrapper.setStartDate(mTimeStart);
}
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updatePacketSize(mUploadFileSize);
}
if (mForceCloseSocket) {
mFtpOutputstream.close();
mReportInterval = false;
if (!mRepeatWrapper.isRepeatUpload()) {
closeExecutors();
}
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList, "");
} else {
for (int i = 0; i < step; i++) {
final byte[] chunk = SpeedTestUtils.readUploadData(mSocketInterface
.getUploadStorageType(),
fileContent,
uploadFile,
mUploadTempFileSize,
uploadChunkSize);
mFtpOutputstream.write(chunk, 0, uploadChunkSize);
mUploadTempFileSize += uploadChunkSize;
mUlComputationTempFileSize += uploadChunkSize;
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updateTempPacketSize(uploadChunkSize);
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int j = 0; j < mListenerList.size(); j++) {
mListenerList.get(j).onProgress(report.getProgressPercent(), report);
}
}
}
if (remain != 0) {
final byte[] chunk = SpeedTestUtils.readUploadData(mSocketInterface
.getUploadStorageType(),
fileContent,
uploadFile,
mUploadTempFileSize,
remain);
mFtpOutputstream.write(chunk, 0, remain);
mUploadTempFileSize += remain;
mUlComputationTempFileSize += remain;
if (mRepeatWrapper.isRepeatUpload()) {
mRepeatWrapper.updateTempPacketSize(remain);
}
}
if (!mReportInterval) {
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int j = 0; j < mListenerList.size(); j++) {
mListenerList.get(j).onProgress(SpeedTestConst.PERCENT_MAX.floatValue(),
report);
}
}
mTimeEnd = System.nanoTime();
mFtpOutputstream.close();
mReportInterval = false;
if (!mRepeatWrapper.isRepeatUpload()) {
closeExecutors();
}
final SpeedTestReport report = getReport(SpeedTestMode.UPLOAD);
for (int i = 0; i < mListenerList.size(); i++) {
mListenerList.get(i).onCompletion(report);
}
}
} else {
mReportInterval = false;
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, "cant create stream" +
" " +
"from uri " + uri + " with reply code : " + ftpClient.getReplyCode());
}
} catch (SocketTimeoutException e) {
//e.printStackTrace();
mReportInterval = false;
mErrorDispatched = true;
if (!mForceCloseSocket) {
SpeedTestUtils.dispatchSocketTimeout(mForceCloseSocket, mListenerList, SpeedTestConst
.SOCKET_WRITE_ERROR);
} else {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, e.getMessage());
}
closeSocket();
closeExecutors();
} catch (IOException e) {
//e.printStackTrace();
mReportInterval = false;
mErrorDispatched = true;
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket,
mListenerList, e.getMessage());
closeExecutors();
} finally {
mErrorDispatched = false;
disconnectFtp(ftpClient);
if (uploadFile != null) {
try {
uploadFile.close();
randomGen.deleteFile();
} catch (IOException e) {
//e.printStackTrace();
}
}
}
}
});
} catch (MalformedURLException e) {
SpeedTestUtils.dispatchError(mSocketInterface, mForceCloseSocket, mListenerList,
SpeedTestError.MALFORMED_URI,
e.getMessage());
}
}
/**
* Close socket streams and mSocket object.
*/
public void closeSocket() {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
}
}
}
/**
* close socket / stop download/upload operations.
*/
public void forceStopTask() {
mForceCloseSocket = true;
if (mFtpInputstream != null) {
try {
mFtpInputstream.close();
} catch (IOException e) {
//e.printStackTrace();
}
}
if (mFtpOutputstream != null) {
try {
mFtpOutputstream.close();
} catch (IOException e) {
//e.printStackTrace();
}
}
}
/**
* Shutdown threadpool and wait for task completion.
*/
public void shutdownAndWait() {
closeExecutors();
try {
mReadExecutorService.awaitTermination(SpeedTestConst.THREADPOOL_WAIT_COMPLETION_MS, TimeUnit.MILLISECONDS);
mWriteExecutorService.awaitTermination(SpeedTestConst.THREADPOOL_WAIT_COMPLETION_MS, TimeUnit.MILLISECONDS);
mReportExecutorService.awaitTermination(SpeedTestConst.THREADPOOL_WAIT_COMPLETION_MS,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//e.printStackTrace();
}
}
/**
* reset report threadpool if necessary.
*/
public void renewReportThreadPool() {
if (mReportExecutorService == null || mReportExecutorService.isShutdown()) {
mReportExecutorService = Executors.newScheduledThreadPool(SpeedTestConst.THREAD_POOL_REPORT_SIZE);
}
}
/**
* retrieve threadpool used to publish reports.
*
* @return report threadpool
*/
public ScheduledExecutorService getReportThreadPool() {
return mReportExecutorService;
}
/**
* Check if report interval is set.
*
* @return report interval
*/
public boolean isReportInterval() {
return mReportInterval;
}
/**
* retrieve current speed test mode.
*
* @return speed test mode (UPLOAD/DOWNLOAD/NONE)
*/
public SpeedTestMode getSpeedTestMode() {
return mSpeedTestMode;
}
}