package org.commcare.tasks;
import android.os.SystemClock;
import org.commcare.CommCareApp;
import org.commcare.engine.resource.AppInstallStatus;
import org.commcare.engine.resource.ResourceInstallUtils;
import org.commcare.engine.resource.installers.LocalStorageUnavailableException;
import org.commcare.logging.AndroidLogger;
import org.commcare.resources.ResourceManager;
import org.commcare.resources.model.InvalidResourceException;
import org.commcare.resources.model.Resource;
import org.commcare.resources.model.ResourceTable;
import org.commcare.resources.model.TableStateListener;
import org.commcare.resources.model.UnresolvedResourceException;
import org.commcare.tasks.templates.CommCareTask;
import org.commcare.utils.AndroidCommCarePlatform;
import org.commcare.xml.CommCareElementParser;
import org.javarosa.core.services.Logger;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import java.util.Vector;
/**
* @author ctsims
*/
public abstract class ResourceEngineTask<R>
extends CommCareTask<String, int[], AppInstallStatus, R>
implements TableStateListener {
private final CommCareApp app;
private static final int PHASE_CHECKING = 0;
public static final int PHASE_DOWNLOAD = 1;
private int installedResourceCountWhileUpdating = 0;
private int installedResourceCount = 0;
private int totalResourceCount = -1;
protected UnresolvedResourceException missingResourceException = null;
protected InvalidResourceException invalidResourceException = null;
protected int badReqCode = -1;
private int phase = -1;
// This boolean is set from CommCareSetupActivity -- If we are in keep
// trying mode for installation, we want to sleep in between attempts to
// launch this task
private final boolean shouldSleep;
protected String vAvailable;
protected String vRequired;
protected boolean majorIsProblem;
private final Object statusLock = new Object();
private boolean statusCheckRunning = false;
public ResourceEngineTask(CommCareApp app, int taskId, boolean shouldSleep) {
this.app = app;
this.taskId = taskId;
this.shouldSleep = shouldSleep;
TAG = ResourceEngineTask.class.getSimpleName();
}
@Override
protected AppInstallStatus doTaskBackground(String... profileRefs) {
String profileRef = profileRefs[0];
ResourceInstallUtils.recordUpdateAttemptTime(app);
app.setupSandbox();
Logger.log(AndroidLogger.TYPE_RESOURCES,
"Beginning install attempt for profile " + profileRefs[0]);
if (shouldSleep) {
SystemClock.sleep(2000);
}
try {
AndroidCommCarePlatform platform = app.getCommCarePlatform();
ResourceTable global = platform.getGlobalResourceTable();
global.setStateListener(this);
try {
ResourceManager.installAppResources(platform, profileRef, global, false);
} catch (LocalStorageUnavailableException e) {
ResourceInstallUtils.logInstallError(e,
"Couldn't install file to local storage|");
return AppInstallStatus.NoLocalStorage;
} catch (UnfullfilledRequirementsException e) {
if (e.isDuplicateException()) {
return AppInstallStatus.DuplicateApp;
} else {
badReqCode = e.getRequirementCode();
vAvailable = e.getAvailableVesionString();
vRequired = e.getRequiredVersionString();
majorIsProblem = e.getRequirementCode() == CommCareElementParser.REQUIREMENT_MAJOR_APP_VERSION;
ResourceInstallUtils.logInstallError(e,
"App resources are incompatible with this device|");
return AppInstallStatus.IncompatibleReqs;
}
} catch (UnresolvedResourceException e) {
AppInstallStatus outcome =
ResourceInstallUtils.processUnresolvedResource(e);
if (outcome != AppInstallStatus.BadCertificate) {
missingResourceException = e;
}
return outcome;
} catch (InvalidResourceException e) {
invalidResourceException = e;
return AppInstallStatus.InvalidResource;
}
ResourceInstallUtils.initAndCommitApp(app, profileRef);
return AppInstallStatus.Installed;
} catch (Exception e) {
e.printStackTrace();
ResourceInstallUtils.logInstallError(e,
"Unknown error ocurred during install|");
return AppInstallStatus.UnknownFailure;
}
}
@Override
public void compoundResourceAdded(final ResourceTable table) {
synchronized (statusLock) {
// if last time isn't set or is less than our spacing count, do not
// perform status update. Also if we are already running one, just skip this.
if (statusCheckRunning) {
return;
}
//Otherwise fire off a new check
Runnable statusUpdateCheck = new Runnable() {
@Override
public void run() {
Vector<Resource> resources;
try {
resources = ResourceManager.getResourceListFromProfile(table);
} catch(Exception e) {
// Since we're on a seperate thread, the db can close during the process
// before we can catch the cancel when the install finishes. If so,
// we can skip the status check entirely.
signalStatusCheckComplete();
return;
}
installedResourceCount = 0;
totalResourceCount = resources.size();
boolean forceClosed = false;
for (Resource r : resources) {
forceClosed = ResourceEngineTask.this.getStatus() == Status.FINISHED ||
ResourceEngineTask.this.isCancelled();
if (forceClosed) {
break;
}
switch (r.getStatus()) {
case Resource.RESOURCE_STATUS_UPGRADE:
// If we spot an upgrade after we've started the upgrade process,
// something now needs to be updated
if (phase == PHASE_CHECKING) {
ResourceEngineTask.this.phase = PHASE_DOWNLOAD;
}
installedResourceCount++;
break;
case Resource.RESOURCE_STATUS_INSTALLED:
installedResourceCount++;
break;
}
}
if (!forceClosed) {
incrementProgress(installedResourceCount, totalResourceCount);
}
signalStatusCheckComplete();
}
private void signalStatusCheckComplete() {
synchronized (statusLock) {
statusCheckRunning = false;
}
}
};
statusCheckRunning = true;
Thread t = new Thread(statusUpdateCheck);
t.start();
}
}
@Override
public void simpleResourceAdded() {
if (statusCheckRunning) {
installedResourceCountWhileUpdating++;
} else {
installedResourceCount += installedResourceCountWhileUpdating + 1;
installedResourceCountWhileUpdating = 0;
incrementProgress(installedResourceCount, totalResourceCount);
}
}
@Override
public void incrementProgress(int complete, int total) {
this.publishProgress(new int[]{complete, total, phase});
}
}