package org.commcare.network;
import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.util.Log;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.commcare.tasks.DataPullTask;
import org.commcare.core.network.bitcache.BitCache;
import org.commcare.core.network.bitcache.BitCacheFactory;
import org.commcare.utils.AndroidCacheDirSetup;
import org.javarosa.core.io.StreamsUtil;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Performs data pulling http request and provides logic to retrieve the
* response into a local cache.
*
* @author Phillip Mates (pmates@dimagi.com).
*/
public class RemoteDataPullResponse {
private final DataPullTask task;
public final int responseCode;
private final HttpResponse response;
/**
* Makes data pulling request and keeps response for local caching
*
* @param task For progress reporting
* @param response Contains data pull response stream and status code
*/
protected RemoteDataPullResponse(DataPullTask task,
HttpResponse response) throws IOException {
this.response = response;
this.responseCode = response.getStatusLine().getStatusCode();
this.task = task;
}
/**
* Retrieves the HttpResponse stream and writes it to an initialized safe
* local cache. Notifies listeners of progress through the download if its
* size is available.
*
* @throws IOException If there is an issue reading or writing the response.
*/
public BitCache writeResponseToCache(Context c) throws IOException {
BitCache cache = null;
try {
final long dataSizeGuess = guessDataSize();
cache = BitCacheFactory.getCache(new AndroidCacheDirSetup(c), dataSizeGuess);
cache.initializeCache();
OutputStream cacheOut = cache.getCacheStream();
InputStream input = getInputStream();
Log.i("commcare-network", "Starting network read, expected content size: " + dataSizeGuess + "b");
StreamsUtil.writeFromInputToOutputNew(new BufferedInputStream(input),
cacheOut,
new StreamsUtil.StreamReadObserver() {
long lastOutput = 0;
/** The notification threshold. **/
static final int PERCENT_INCREASE_THRESHOLD = 4;
@Override
public void notifyCurrentCount(long bytesRead) {
boolean notify;
//We always wanna notify when we get our first bytes
if (lastOutput == 0) {
Log.i("commcare-network", "First" + bytesRead + " bytes received from network: ");
}
//After, if we don't know how much data to expect, we can't do
//anything useful
if (dataSizeGuess == -1) {
//set this so the first notification up there doesn't keep firing
lastOutput = bytesRead;
return;
}
int percentIncrease = (int)(((bytesRead - lastOutput) * 100) / dataSizeGuess);
//Now see if we're over the reporting threshold
//TODO: Is this actually necessary? In theory this shouldn't
//matter due to android task polling magic?
notify = percentIncrease > PERCENT_INCREASE_THRESHOLD;
if (notify && task != null) {
lastOutput = bytesRead;
int totalRead = (int)(((bytesRead) * 100) / dataSizeGuess);
task.reportDownloadProgress(totalRead);
}
}
});
return cache;
//If something goes wrong while we're reading into the cache
//we may need to free the storage we reserved.
} catch (IOException e) {
cache.release();
throw e;
}
}
protected InputStream getInputStream() throws IOException {
return AndroidHttpClient.getUngzippedContent(response.getEntity());
}
public String getShortBody() throws IOException {
return new String(StreamsUtil.inputStreamToByteArray(AndroidHttpClient.getUngzippedContent(response.getEntity())));
}
/**
* Get an estimation of how large the provided response is.
*
* @return -1 for unknown.
*/
protected long guessDataSize() {
if (response.containsHeader("Content-Length")) {
String length = response.getFirstHeader("Content-Length").getValue();
try {
return Long.parseLong(length);
} catch (Exception e) {
//Whatever.
}
}
return -1;
}
public Header getRetryHeader() {
return response.getFirstHeader("Retry-After");
}
}