package org.commcare.dalvik.application;
import java.io.File;
import net.sqlcipher.database.SQLiteDatabase;
import org.commcare.android.database.DbHelper;
import org.commcare.android.database.SqlStorage;
import org.commcare.android.database.app.DatabaseAppOpenHelper;
import org.commcare.android.database.global.models.ApplicationRecord;
import org.commcare.android.javarosa.AndroidLogger;
import org.commcare.android.logic.GlobalConstants;
import org.commcare.android.references.JavaFileRoot;
import org.commcare.android.storage.framework.Table;
import org.commcare.android.util.AndroidCommCarePlatform;
import org.commcare.android.util.SessionUnavailableException;
import org.commcare.dalvik.preferences.CommCarePreferences;
import org.commcare.resources.model.Resource;
import org.commcare.resources.model.ResourceTable;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.ReferenceManager;
import org.javarosa.core.services.Logger;
import org.javarosa.core.services.locale.Localization;
import org.javarosa.core.services.storage.Persistable;
import org.javarosa.core.services.storage.StorageFullException;
import org.javarosa.core.util.UnregisteredLocaleException;
import android.content.SharedPreferences;
/**
*
* This (awkwardly named!) container is responsible for keeping track of a single
* CommCare "App". It should be able to set up an App, break it back down, and
* maintain all of the code needed to sandbox applicaitons
*
* @author ctsims
*
*/
public class CommCareApp {
ApplicationRecord record;
JavaFileRoot fileRoot;
AndroidCommCarePlatform platform;
public static Object lock = new Object();
/** This unfortunately can't be managed entirely by the application object, so we have to do some here **/
public static CommCareApp currentSandbox;
private Object appDbHandleLock = new Object();
private SQLiteDatabase appDatabase;
public CommCareApp(ApplicationRecord record) {
this.record = record;
//Now, we need to identify the state of the application resources
int[] version = CommCareApplication._().getCommCareVersion();
//TODO: Badly coupled
platform = new AndroidCommCarePlatform(version[0], version[1], CommCareApplication._(), this);
}
public String storageRoot() {
//This External Storage Directory will always destroy your data when you upgrade, which is stupid. Unfortunately
//it's also largely unavoidable until Froyo's fix for this problem makes it to the phones. For now we're going
//to rely on the fact that the phone knows how to fix missing/corrupt directories every time it upgrades.
return CommCareApplication._().getAndroidFsRoot() + "app/" + record.getApplicationId() + "/";
}
public void createPaths() {
String[] paths = new String[] {"", GlobalConstants.FILE_CC_INSTALL, GlobalConstants.FILE_CC_UPGRADE, GlobalConstants.FILE_CC_CACHE, GlobalConstants.FILE_CC_FORMS, GlobalConstants.FILE_CC_MEDIA, GlobalConstants.FILE_CC_LOGS, GlobalConstants.FILE_CC_ATTACHMENTS};
for(String path : paths) {
File f = new File(fsPath(path));
if(!f.exists()) {
f.mkdirs();
}
}
}
public String fsPath(String relative) {
return storageRoot() + relative;
}
private void initializeFileRoots() {
synchronized(lock) {
String root = storageRoot();
fileRoot = new JavaFileRoot(root);
String testFileRoot = "jr://file/mytest.file";
//Assertion: There should be _no_ other file roots when we initialize
try {
String testFilePath = ReferenceManager._().DeriveReference(testFileRoot).getLocalURI();
String message = "Cannot setup sandbox. An Existing file root is set up, which directs to: " +testFilePath;
Logger.log(AndroidLogger.TYPE_ERROR_DESIGN, message);
throw new IllegalStateException(message);
} catch(InvalidReferenceException ire) {
//Expected.
}
ReferenceManager._().addReferenceFactory(fileRoot);
//Double check that things point to the right place?
}
}
public SharedPreferences getAppPreferences() {
return CommCareApplication._().getSharedPreferences(getPreferencesFilename(), 0);
}
public void setupSandbox() {
setupSandbox(true);
}
/**
* @param createFilePaths True if file paths should be created as usual. False otherwise
*/
public void setupSandbox(boolean createFilePaths) {
synchronized(lock) {
Logger.log(AndroidLogger.TYPE_RESOURCES, "Staging Sandbox: " + record.getApplicationId());
if(currentSandbox != null) {
currentSandbox.teardownSandbox();
}
//general setup
if(createFilePaths) {
createPaths();
}
initializeFileRoots();
currentSandbox = this;
}
}
public boolean initializeApplication() {
setupSandbox();
ResourceTable global = platform.getGlobalResourceTable();
ResourceTable upgrade = platform.getUpgradeResourceTable();
ResourceTable recovery = platform.getRecoveryTable();
System.out.println("Global");
System.out.println(global.toString());
System.out.println("upgrade");
System.out.println(upgrade.toString());
System.out.println("recovery");
System.out.println(recovery.toString());
/**
* See if any of our tables got left in a weird state
*/
if(global.getTableReadiness() == ResourceTable.RESOURCE_TABLE_UNCOMMITED) {
global.rollbackCommits();
System.out.println("Global after rollback");
System.out.println(global.toString());
}
if(upgrade.getTableReadiness() == ResourceTable.RESOURCE_TABLE_UNCOMMITED) {
upgrade.rollbackCommits();
System.out.println("upgrade after rollback");
System.out.println(upgrade.toString());
}
/**
* See if we got left in the middle of an update
*/
if(global.getTableReadiness() == ResourceTable.RESOURCE_TABLE_UNSTAGED) {
//If so, repair the global table. (Always takes priority over maintaining
//the update)
global.repairTable(upgrade);
}
//TODO: This, but better.
Resource profile = global.getResourceWithId("commcare-application-profile");
if(profile != null && profile.getStatus() == Resource.RESOURCE_STATUS_INSTALLED) {
platform.initialize(global);
try{
Localization.setLocale(getAppPreferences().getString("cur_locale", "default"));
}catch(UnregisteredLocaleException urle) {
Localization.setLocale(Localization.getGlobalLocalizerAdvanced().getAvailableLocales()[0]);
}
return true;
}
return false;
}
public boolean areResourcesValidated(){
SharedPreferences appPreferences = getAppPreferences();
return appPreferences.getBoolean("isValidated",false) || appPreferences.getString(CommCarePreferences.CONTENT_VALIDATED, "no").equals(CommCarePreferences.YES);
}
public void setResourcesValidated(boolean isValidated){
SharedPreferences.Editor editor = getAppPreferences().edit();
editor.putBoolean("isValidated", isValidated);
editor.commit();
}
public void teardownSandbox() {
synchronized(lock) {
Logger.log(AndroidLogger.TYPE_RESOURCES, "Tearing down sandbox: " + record.getApplicationId());
ReferenceManager._().removeReferenceFactory(fileRoot);
synchronized(appDbHandleLock) {
if(appDatabase != null) {
appDatabase.close();
}
appDatabase = null;
}
}
}
public AndroidCommCarePlatform getCommCarePlatform() {
return platform;
}
public <T extends Persistable> SqlStorage<T> getStorage(Class<T> c) throws SessionUnavailableException {
return getStorage(c.getAnnotation(Table.class).value(), c);
}
public <T extends Persistable> SqlStorage<T> getStorage(String name, Class<T> c) throws SessionUnavailableException {
return new SqlStorage<T>(name, c, new DbHelper(CommCareApplication._().getApplicationContext()){
/*
* (non-Javadoc)
* @see org.commcare.android.database.DbHelper#getHandle()
*/
@Override
public SQLiteDatabase getHandle() {
synchronized(appDbHandleLock) {
if(appDatabase == null || !appDatabase.isOpen()) {
appDatabase = new DatabaseAppOpenHelper(this.c, record.getApplicationId()).getWritableDatabase("null");
}
return appDatabase;
}
}
});
}
public void clearInstallData() {
ResourceTable global = platform.getGlobalResourceTable();
//Install was botched, clear anything left lying around....
global.clear();
}
public void writeInstalled() {
record.setStatus(ApplicationRecord.STATUS_INSTALLED);
try {
CommCareApplication._().getGlobalStorage(ApplicationRecord.class).write(record);
} catch (StorageFullException e) {
throw new RuntimeException(e);
}
}
public String getPreferencesFilename(){
return record.getApplicationId();
}
}