package org.commcare.tasks;
import org.apache.http.Header;
import org.commcare.logging.AndroidLogger;
import org.commcare.network.RemoteDataPullResponse;
import org.javarosa.core.services.Logger;
import org.javarosa.xml.ElementParser;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
/**
* Keeps tracking of everything DataPullTask needs to know and do in the case that the server is
* performing an async restore, including parsing the retry response and reporting incremental
* server progress during the retry wait period
*/
public class AsyncRestoreHelper {
private final DataPullTask syncTask;
public long retryAtTime = -1;
public int serverProgressCompletedSoFar = -1;
private int serverProgressTotal = -1;
private int lastReportedServerProgressValue = 0;
public AsyncRestoreHelper(DataPullTask task) {
this.syncTask = task;
}
protected ResultAndError<DataPullTask.PullTaskResult> handleRetryResponseCode(RemoteDataPullResponse response) {
Header retryHeader = response.getRetryHeader();
if (retryHeader == null) {
return new ResultAndError<>(DataPullTask.PullTaskResult.BAD_DATA);
}
try {
long waitTimeInMilliseconds = Integer.parseInt(retryHeader.getValue()) * 1000;
retryAtTime = System.currentTimeMillis() + waitTimeInMilliseconds;
if (!parseProgressFromRetryResult(response)) {
return new ResultAndError<>(DataPullTask.PullTaskResult.BAD_DATA);
}
return new ResultAndError<>(DataPullTask.PullTaskResult.RETRY_NEEDED);
} catch (NumberFormatException e) {
Logger.log(AndroidLogger.TYPE_USER, "Invalid Retry-After header value: "
+ retryHeader.getValue());
return new ResultAndError<>(DataPullTask.PullTaskResult.BAD_DATA);
}
}
private boolean parseProgressFromRetryResult(RemoteDataPullResponse response) {
try {
InputStream stream = response.writeResponseToCache(syncTask.context).retrieveCache();
KXmlParser parser = ElementParser.instantiateParser(stream);
parser.next();
int eventType = parser.getEventType();
do {
if (eventType == KXmlParser.START_TAG) {
if (parser.getName().toLowerCase().equals("progress")) {
serverProgressCompletedSoFar = Integer.parseInt(
parser.getAttributeValue(null, "done"));
serverProgressTotal = Integer.parseInt(
parser.getAttributeValue(null, "total"));
return true;
}
}
eventType = parser.next();
} while (eventType != KXmlParser.END_DOCUMENT);
} catch (IOException | XmlPullParserException e) {
Logger.log(AndroidLogger.TYPE_USER,
"Error while parsing progress values of retry result");
}
return false;
}
/**
* In order to achieve the impression of the progress bar updating smoothly during the wait
* period after a retry response is received, use a timer task to report incremental progress
* from the state we were last in to the progress value that the server just reported back to
* us, splitting up the wait time into uniform time periods for reporting each unit of progress.
*/
protected void startReportingServerProgress() {
long millisUntilNextAttempt = retryAtTime - System.currentTimeMillis();
int amountOfProgressToCoverThisCycle =
serverProgressCompletedSoFar - lastReportedServerProgressValue;
if (amountOfProgressToCoverThisCycle <= 0) {
syncTask.reportServerProgress(lastReportedServerProgressValue, serverProgressTotal);
return;
}
long intervalAllottedPerProgressUnit =
millisUntilNextAttempt / amountOfProgressToCoverThisCycle;
final Timer reportServerProgressTimer = new Timer();
reportServerProgressTimer.schedule(new TimerTask() {
@Override
public void run() {
if (lastReportedServerProgressValue == serverProgressCompletedSoFar) {
reportServerProgressTimer.cancel();
reportServerProgressTimer.purge();
return;
}
syncTask.reportServerProgress(++lastReportedServerProgressValue, serverProgressTotal);
}
}, 0, intervalAllottedPerProgressUnit);
}
/**
* If we were showing a progress bar for server progress, make it fill up before we proceed
*/
protected void completeServerProgressBarIfShowing() {
if (lastReportedServerProgressValue > 0) {
syncTask.reportServerProgress(serverProgressTotal, serverProgressTotal);
}
}
protected boolean retryWaitPeriodInProgress() {
return retryAtTime != -1 && retryAtTime > System.currentTimeMillis();
}
}