From f360a9680c83b986f3f293c2c1f7cc658efb8d26 Mon Sep 17 00:00:00 2001 From: Shubham Goyal Date: Fri, 2 Aug 2024 15:50:21 +0530 Subject: [PATCH 1/2] Decouple form save code from async task --- .../activities/FormEntryActivity.java | 11 +- .../org/commcare/tasks/FormSaveHelper.java | 259 ++++++++++++++++++ .../org/commcare/tasks/SaveToDiskTask.java | 223 +-------------- 3 files changed, 270 insertions(+), 223 deletions(-) create mode 100644 app/src/org/commcare/tasks/FormSaveHelper.java diff --git a/app/src/org/commcare/activities/FormEntryActivity.java b/app/src/org/commcare/activities/FormEntryActivity.java index f127f1b6a..6a1e6ca64 100644 --- a/app/src/org/commcare/activities/FormEntryActivity.java +++ b/app/src/org/commcare/activities/FormEntryActivity.java @@ -61,6 +61,7 @@ import org.commcare.services.FCMMessageData; import org.commcare.services.PendingSyncAlertBroadcastReceiver; import org.commcare.tasks.FormLoaderTask; +import org.commcare.tasks.FormSaveHelper; import org.commcare.tasks.SaveToDiskTask; import org.commcare.tts.TextToSpeechCallback; import org.commcare.tts.TextToSpeechConverter; @@ -829,10 +830,12 @@ private void saveDataToDisk(boolean exit, boolean complete, String updatedSaveNa return; } - mSaveToDiskTask = new SaveToDiskTask(getIntent().getIntExtra(KEY_FORM_RECORD_ID, -1), - getIntent().getIntExtra(KEY_FORM_DEF_ID, -1), - FormEntryInstanceState.mFormRecordPath, - exit, complete, updatedSaveName, symetricKey, headless); + int formRecordId = getIntent().getIntExtra(KEY_FORM_RECORD_ID, -1); + int formDefId = getIntent().getIntExtra(KEY_FORM_DEF_ID, -1); + FormSaveHelper formSaveHelper = new FormSaveHelper(formRecordId, formDefId, exit, complete, + updatedSaveName, symetricKey, FormEntryInstanceState.mFormRecordPath); + mSaveToDiskTask = new SaveToDiskTask(formSaveHelper, headless); + if (!headless) { mSaveToDiskTask.connect(this); } diff --git a/app/src/org/commcare/tasks/FormSaveHelper.java b/app/src/org/commcare/tasks/FormSaveHelper.java new file mode 100644 index 000000000..198b2b2e1 --- /dev/null +++ b/app/src/org/commcare/tasks/FormSaveHelper.java @@ -0,0 +1,259 @@ +package org.commcare.tasks; + +import org.commcare.CommCareApplication; +import org.commcare.activities.FormEntryActivity; +import org.commcare.activities.components.ImageCaptureProcessing; +import org.commcare.android.database.app.models.FormDefRecord; +import org.commcare.android.database.user.models.FormRecord; +import org.commcare.android.logging.ForceCloseLogger; +import org.commcare.logging.XPathErrorLogger; +import org.commcare.models.database.SqlStorage; +import org.commcare.models.encryption.EncryptionIO; +import org.commcare.util.FormMetaIndicatorUtil; +import org.commcare.util.LogTypes; +import org.commcare.utils.FileUtil; +import org.javarosa.core.io.StreamsUtil; +import org.javarosa.core.model.FormIndex; +import org.javarosa.core.model.instance.FormInstance; +import org.javarosa.core.model.instance.TreeReference; +import org.javarosa.core.services.Logger; +import org.javarosa.core.services.locale.Localization; +import org.javarosa.core.services.transport.payload.ByteArrayPayload; +import org.javarosa.form.api.FormController; +import org.javarosa.form.api.FormEntryController; +import org.javarosa.model.xform.XFormSerializingVisitor; +import org.javarosa.xform.util.XFormSerializer; +import org.javarosa.xpath.XPathException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.crypto.spec.SecretKeySpec; + +public class FormSaveHelper { + + private final Boolean mExitAfterSave; + private final Boolean mMarkCompleted; + private final int mFormRecordId; + private final int mFormDefId; + // The name of the form we are saving + private final String mRecordName; + private final String mFormRecordPath; + + private final SecretKeySpec mSymetricKey; + + public FormSaveHelper(int formRecordId, int formDefId, Boolean exitAfterSave, Boolean markCompleted, + String recordName, SecretKeySpec symetricKey, String formRecordPath) { + mFormRecordId = formRecordId; + mFormDefId = formDefId; + mMarkCompleted = markCompleted; + mExitAfterSave = exitAfterSave; + mRecordName = recordName; + mFormRecordPath = formRecordPath; + mSymetricKey = symetricKey; + } + + public ResultAndError saveForm() { + long time = System.currentTimeMillis(); + try { + if (hasInvalidAnswers(mMarkCompleted)) { + return new ResultAndError<>(SaveToDiskTask.SaveStatus.INVALID_ANSWER); + } + } catch (XPathException xpe) { + String cleanedMessage = "An error in your form prevented it from saving: \n" + + xpe.getMessage(); + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVE_ERROR, cleanedMessage); + } + + FormEntryActivity.mFormController.postProcessInstance(); + + try { + exportData(mMarkCompleted); + } catch (FileNotFoundException e) { + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVE_ERROR, + "Something is blocking acesss to the submission file in " + mFormRecordPath); + } catch (XFormSerializer.UnsupportedUnicodeSurrogatesException e) { + Logger.log( + LogTypes.TYPE_ERROR_CONFIG_STRUCTURE, "Form contains invalid data encoding\n\n" + ForceCloseLogger.getStackTrace(e)); + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVE_ERROR, + Localization.get("form.entry.save.invalid.unicode", e.getMessage())); + } catch (IOException e) { + Logger.log(LogTypes.TYPE_ERROR_STORAGE, "I/O Error when serializing form\n\n" + ForceCloseLogger.getStackTrace(e)); + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVE_ERROR, + "Unable to write xml to " + mFormRecordPath); + } catch (FormInstanceTransactionException e) { + // Passing exceptions through content providers make error message strings messy. + String cleanedMessage = e.getMessage().replace("java.lang.IllegalStateException: ", ""); + // Likely a user level issue, so send error to HQ as a app build error + XPathErrorLogger.INSTANCE.logErrorToCurrentApp(cleanedMessage); + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVE_ERROR, cleanedMessage); + } + + if (mMarkCompleted) { + FormEntryActivity.mFormController.markCompleteFormAsSaved(); + } + + logFormSave(mExitAfterSave); + if (mExitAfterSave) { + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVED_AND_EXIT); + } else if (mMarkCompleted) { + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVED_COMPLETE); + } else { + return new ResultAndError<>(SaveToDiskTask.SaveStatus.SAVED_INCOMPLETE); + } + } + + private void logFormSave(boolean exit) { + FormRecord saved = CommCareApplication.instance().getCurrentSessionWrapper().getFormRecord(); + String log = String.format("Form Entry Completed: Record with id %s was saved as %s", saved.getInstanceID(), mMarkCompleted ? "complete" : "incomplete"); + if(exit){ + log += " with user exiting"; + } + Logger.log(LogTypes.TYPE_FORM_ENTRY, log); + } + + /** + * Update form Record with necessary params + */ + private void updateFormRecord(SqlStorage formRecordStorage, boolean incomplete) + throws FormInstanceTransactionException { + + String status; + if (incomplete || !mMarkCompleted) { + status = FormRecord.STATUS_INCOMPLETE; + } else { + status = FormRecord.STATUS_COMPLETE; + } + + // Insert or update the form instance into the database. + FormRecord formRecord = null; + String recordName = mRecordName; + if (mFormRecordId != -1) { + // We started with a concrete instance (i.e. by editing an existing form) + formRecord = FormRecord.getFormRecord(formRecordStorage, mFormRecordId); + } else if (mFormDefId != -1) { + // We started with an empty form or possibly a manually saved form + formRecord = CommCareApplication.instance().getCurrentSessionWrapper().getFormRecord(); + formRecord.setFilePath(mFormRecordPath); + if (recordName == null) { + FormDefRecord formDefRecord = FormDefRecord.getFormDef( + CommCareApplication.instance().getAppStorage(FormDefRecord.class), mFormDefId); + recordName = formDefRecord.getDisplayName(); + } + } + + if (formRecord != null) { + try { + formRecord.setDisplayName(recordName); + String caseName = FormMetaIndicatorUtil.getPragma( + FormMetaIndicatorUtil.FORM_DESCRIPTOR, + FormEntryActivity.mFormController.getFormDef(), + TreeReference.rootRef()); + formRecord.setDescriptor(caseName); + formRecord.updateStatus(formRecordStorage, status); + } catch (IllegalStateException e) { + throw new FormInstanceTransactionException(e); + } + } + } + + /** + * Write's the data to the sdcard, + * In theory we don't have to write to disk, and this is where + * you'd add other methods. + * + * @throws IOException Issue serializing form and + * storing to filesystem + * @throws FormInstanceTransactionException Issue performing transactions + * associated with form saving, + * like case updates and updating + * the associated form record + */ + private void exportData(boolean markCompleted) + throws IOException, FormInstanceTransactionException { + + FormInstance dataModel = FormEntryActivity.mFormController.getInstance(); + XFormSerializingVisitor serializer = new XFormSerializingVisitor(markCompleted); + ByteArrayPayload payload = (ByteArrayPayload)serializer.createSerializedPayload(dataModel); + + writeXmlToStream(payload, + EncryptionIO.createFileOutputStream(mFormRecordPath, mSymetricKey)); + + SqlStorage formRecordStorage = CommCareApplication.instance().getUserStorage(FormRecord.class); + updateFormRecord(formRecordStorage, true); + + if (markCompleted) { + payload = FormEntryActivity.mFormController.getSubmissionXml(); + File instanceXml = new File(mFormRecordPath); + File submissionXml = new File(instanceXml.getParentFile(), "submission.xml"); + // write out submission.xml -- the data to actually submit to aggregate + writeXmlToStream(payload, + EncryptionIO.createFileOutputStream(submissionXml.getAbsolutePath(), mSymetricKey)); + + // Set this record's status to COMPLETE + updateFormRecord(formRecordStorage, false); + + // delete the restore Xml file. + if (!instanceXml.delete()) { + Logger.log(LogTypes.TYPE_MAINTENANCE, + "Error deleting " + instanceXml.getAbsolutePath() + + " prior to renaming submission.xml"); + return; + } + + // rename the submission.xml to be the instanceXml + if (!submissionXml.renameTo(instanceXml)) { + Logger.log(LogTypes.TYPE_MAINTENANCE, + "Error renaming submission.xml to " + instanceXml.getAbsolutePath()); + } + + String rawDirPath = ImageCaptureProcessing.getRawDirectoryPath(instanceXml.getParent()); + if(!FileUtil.deleteFileOrDir(rawDirPath)){ + Logger.log(LogTypes.TYPE_MAINTENANCE, "Error deleting raw dir at path " + rawDirPath); + } + } + } + + private void writeXmlToStream(ByteArrayPayload payload, OutputStream output) throws IOException { + try { + InputStream is = payload.getPayloadStream(); + StreamsUtil.writeFromInputToOutput(is, output); + } finally { + output.close(); + } + } + + /** + * Goes through the entire form to make sure all entered answers comply + * with their constraints. Constraints are ignored on 'jump to', so + * answers can be outside of constraints. We don't allow saving to disk, + * though, until all answers conform to their constraints/requirements. + */ + private boolean hasInvalidAnswers(boolean markCompleted) { + FormController formController = FormEntryActivity.mFormController; + FormIndex currentFormIndex = FormIndex.createBeginningOfFormIndex(); + int event; + while ((event = formController.getEvent(currentFormIndex)) != FormEntryController.EVENT_END_OF_FORM) { + if (event == FormEntryController.EVENT_QUESTION) { + int saveStatus = formController.checkCurrentQuestionConstraint(currentFormIndex); + if (markCompleted && + (saveStatus == FormEntryController.ANSWER_REQUIRED_BUT_EMPTY || + saveStatus == FormEntryController.ANSWER_CONSTRAINT_VIOLATED)) { + formController.jumpToIndex(currentFormIndex); + return true; + } + } + currentFormIndex = formController.getNextFormIndex(currentFormIndex, FormEntryController.STEP_INTO_GROUP, true); + } + return false; + } + + private static class FormInstanceTransactionException extends Exception { + FormInstanceTransactionException(Throwable throwable) { + super(throwable); + } + } +} diff --git a/app/src/org/commcare/tasks/SaveToDiskTask.java b/app/src/org/commcare/tasks/SaveToDiskTask.java index 64091c4a5..44dbe4990 100644 --- a/app/src/org/commcare/tasks/SaveToDiskTask.java +++ b/app/src/org/commcare/tasks/SaveToDiskTask.java @@ -1,7 +1,5 @@ package org.commcare.tasks; -import android.util.Log; - import org.commcare.CommCareApplication; import org.commcare.activities.FormEntryActivity; import org.commcare.activities.components.ImageCaptureProcessing; @@ -46,15 +44,8 @@ public class SaveToDiskTask extends CommCareTask, FormEntryActivity> { // callback to run upon saving private FormSavedListener mSavedListener; - private final Boolean exitAfterSave; - private final Boolean mMarkCompleted; - private final int mFormRecordId; - private final int mFormDefId; - // The name of the form we are saving - private final String mRecordName; - private final String mFormRecordPath; + private final FormSaveHelper mFormSaveHelper; - private final SecretKeySpec symetricKey; public enum SaveStatus { SAVED_COMPLETE, @@ -66,22 +57,12 @@ public enum SaveStatus { public static final int SAVING_TASK_ID = 17; - public SaveToDiskTask(int formRecordId, int formDefId, String formRecordPath, Boolean saveAndExit, Boolean markCompleted, - String updatedName, - SecretKeySpec symetricKey, boolean headless) { + public SaveToDiskTask(FormSaveHelper formSaveHelper, boolean headless) { TAG = SaveToDiskTask.class.getSimpleName(); - - mFormRecordId = formRecordId; - mFormDefId = formDefId; - exitAfterSave = saveAndExit; - mMarkCompleted = markCompleted; - mRecordName = updatedName; - this.symetricKey = symetricKey; - mFormRecordPath = formRecordPath; + mFormSaveHelper = formSaveHelper; if (headless) { this.taskId = -1; - //Don't block on the UI thread if there's no available screen to connect to this.setConnectionTimeout(0); } else { @@ -95,180 +76,14 @@ public SaveToDiskTask(int formRecordId, int formDefId, String formRecordPath, Bo */ @Override protected ResultAndError doTaskBackground(Void... nothing) { - try { - if (hasInvalidAnswers(mMarkCompleted)) { - return new ResultAndError<>(SaveStatus.INVALID_ANSWER); - } - } catch (XPathException xpe) { - String cleanedMessage = "An error in your form prevented it from saving: \n" + - xpe.getMessage(); - return new ResultAndError<>(SaveStatus.SAVE_ERROR, cleanedMessage); - } - - FormEntryActivity.mFormController.postProcessInstance(); - - try { - exportData(mMarkCompleted); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new ResultAndError<>(SaveStatus.SAVE_ERROR, - "Something is blocking acesss to the submission file in " + mFormRecordPath); - } catch (XFormSerializer.UnsupportedUnicodeSurrogatesException e) { - Logger.log(LogTypes.TYPE_ERROR_CONFIG_STRUCTURE, "Form contains invalid data encoding\n\n" + ForceCloseLogger.getStackTrace(e)); - return new ResultAndError<>(SaveStatus.SAVE_ERROR, - Localization.get("form.entry.save.invalid.unicode", e.getMessage())); - } catch (IOException e) { - Logger.log(LogTypes.TYPE_ERROR_STORAGE, "I/O Error when serializing form\n\n" + ForceCloseLogger.getStackTrace(e)); - return new ResultAndError<>(SaveStatus.SAVE_ERROR, - "Unable to write xml to " + mFormRecordPath); - } catch (FormInstanceTransactionException e) { - e.printStackTrace(); - // Passing exceptions through content providers make error message strings messy. - String cleanedMessage = e.getMessage().replace("java.lang.IllegalStateException: ", ""); - // Likely a user level issue, so send error to HQ as a app build error - XPathErrorLogger.INSTANCE.logErrorToCurrentApp(cleanedMessage); - - return new ResultAndError<>(SaveStatus.SAVE_ERROR, cleanedMessage); - } - - if (mMarkCompleted) { - FormEntryActivity.mFormController.markCompleteFormAsSaved(); - } - - logFormSave(exitAfterSave); - if (exitAfterSave) { - return new ResultAndError<>(SaveStatus.SAVED_AND_EXIT); - } else if (mMarkCompleted) { - return new ResultAndError<>(SaveStatus.SAVED_COMPLETE); - } else { - return new ResultAndError<>(SaveStatus.SAVED_INCOMPLETE); - } + return mFormSaveHelper.saveForm(); } - private void logFormSave(boolean exit) { - FormRecord saved = CommCareApplication.instance().getCurrentSessionWrapper().getFormRecord(); - String log = String.format("Form Entry Completed: Record with id %s was saved as %s", saved.getInstanceID(), mMarkCompleted ? "complete" : "incomplete"); - if(exit){ - log += " with user exiting"; - } - Logger.log(LogTypes.TYPE_FORM_ENTRY, log); - } - - /** - * Update form Record with necessary params - */ - private void updateFormRecord(SqlStorage formRecordStorage, boolean incomplete) - throws FormInstanceTransactionException { - - String status; - if (incomplete || !mMarkCompleted) { - status = FormRecord.STATUS_INCOMPLETE; - } else { - status = FormRecord.STATUS_COMPLETE; - } - // Insert or update the form instance into the database. - FormRecord formRecord = null; - String recordName = mRecordName; - if (mFormRecordId != -1) { - // We started with a concrete instance (i.e. by editing an existing form) - formRecord = FormRecord.getFormRecord(formRecordStorage, mFormRecordId); - } else if (mFormDefId != -1) { - // We started with an empty form or possibly a manually saved form - formRecord = CommCareApplication.instance().getCurrentSessionWrapper().getFormRecord(); - formRecord.setFilePath(mFormRecordPath); - if (recordName == null) { - FormDefRecord formDefRecord = FormDefRecord.getFormDef( - CommCareApplication.instance().getAppStorage(FormDefRecord.class), mFormDefId); - recordName = formDefRecord.getDisplayName(); - } - } - - if (formRecord != null) { - try { - formRecord.setDisplayName(recordName); - String caseName = FormMetaIndicatorUtil.getPragma( - FormMetaIndicatorUtil.FORM_DESCRIPTOR, - FormEntryActivity.mFormController.getFormDef(), - TreeReference.rootRef()); - formRecord.setDescriptor(caseName); - formRecord.updateStatus(formRecordStorage, status); - } catch (IllegalStateException e) { - throw new FormInstanceTransactionException(e); - } - } - } - - /** - * Write's the data to the sdcard, - * In theory we don't have to write to disk, and this is where - * you'd add other methods. - * - * @throws IOException Issue serializing form and - * storing to filesystem - * @throws FormInstanceTransactionException Issue performing transactions - * associated with form saving, - * like case updates and updating - * the associated form record - */ - private void exportData(boolean markCompleted) - throws IOException, FormInstanceTransactionException { - - FormInstance dataModel = FormEntryActivity.mFormController.getInstance(); - XFormSerializingVisitor serializer = new XFormSerializingVisitor(markCompleted); - ByteArrayPayload payload = (ByteArrayPayload)serializer.createSerializedPayload(dataModel); - - writeXmlToStream(payload, - EncryptionIO.createFileOutputStream(mFormRecordPath, symetricKey)); - - SqlStorage formRecordStorage = CommCareApplication.instance().getUserStorage(FormRecord.class); - updateFormRecord(formRecordStorage, true); - - if (markCompleted) { - payload = FormEntryActivity.mFormController.getSubmissionXml(); - File instanceXml = new File(mFormRecordPath); - File submissionXml = new File(instanceXml.getParentFile(), "submission.xml"); - // write out submission.xml -- the data to actually submit to aggregate - writeXmlToStream(payload, - EncryptionIO.createFileOutputStream(submissionXml.getAbsolutePath(), symetricKey)); - - // Set this record's status to COMPLETE - updateFormRecord(formRecordStorage, false); - - // delete the restore Xml file. - if (!instanceXml.delete()) { - Logger.log(LogTypes.TYPE_MAINTENANCE, - "Error deleting " + instanceXml.getAbsolutePath() - + " prior to renaming submission.xml"); - return; - } - - // rename the submission.xml to be the instanceXml - if (!submissionXml.renameTo(instanceXml)) { - Logger.log(LogTypes.TYPE_MAINTENANCE, - "Error renaming submission.xml to " + instanceXml.getAbsolutePath()); - } - - String rawDirPath = ImageCaptureProcessing.getRawDirectoryPath(instanceXml.getParent()); - if(!FileUtil.deleteFileOrDir(rawDirPath)){ - Logger.log(LogTypes.TYPE_MAINTENANCE, "Error deleting raw dir at path " + rawDirPath); - } - } - } - - private void writeXmlToStream(ByteArrayPayload payload, OutputStream output) throws IOException { - try { - InputStream is = payload.getPayloadStream(); - StreamsUtil.writeFromInputToOutput(is, output); - } finally { - output.close(); - } - } @Override protected void onPostExecute(ResultAndError result) { super.onPostExecute(result); - synchronized (this) { if (mSavedListener != null) { if (result == null) { @@ -298,34 +113,4 @@ public void setFormSavedListener(FormSavedListener fsl) { } } - /** - * Goes through the entire form to make sure all entered answers comply - * with their constraints. Constraints are ignored on 'jump to', so - * answers can be outside of constraints. We don't allow saving to disk, - * though, until all answers conform to their constraints/requirements. - */ - private boolean hasInvalidAnswers(boolean markCompleted) { - FormController formController = FormEntryActivity.mFormController; - FormIndex currentFormIndex = FormIndex.createBeginningOfFormIndex(); - int event; - while ((event = formController.getEvent(currentFormIndex)) != FormEntryController.EVENT_END_OF_FORM) { - if (event == FormEntryController.EVENT_QUESTION) { - int saveStatus = formController.checkCurrentQuestionConstraint(currentFormIndex); - if (markCompleted && - (saveStatus == FormEntryController.ANSWER_REQUIRED_BUT_EMPTY || - saveStatus == FormEntryController.ANSWER_CONSTRAINT_VIOLATED)) { - formController.jumpToIndex(currentFormIndex); - return true; - } - } - currentFormIndex = formController.getNextFormIndex(currentFormIndex, FormEntryController.STEP_INTO_GROUP, true); - } - return false; - } - - private static class FormInstanceTransactionException extends Exception { - FormInstanceTransactionException(Throwable throwable) { - super(throwable); - } - } } From 17f8ef34ee6f175a3ba2691720f11b3f316cff2f Mon Sep 17 00:00:00 2001 From: Shubham Goyal Date: Fri, 2 Aug 2024 16:24:51 +0530 Subject: [PATCH 2/2] Makes FormSaveHelper a singleton and manage it's instance during form save task --- .../activities/FormEntryActivity.java | 11 ++++-- .../org/commcare/tasks/FormSaveHelper.java | 26 +++++++++++++- .../org/commcare/tasks/SaveToDiskTask.java | 36 ++++--------------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/app/src/org/commcare/activities/FormEntryActivity.java b/app/src/org/commcare/activities/FormEntryActivity.java index 6a1e6ca64..bad159677 100644 --- a/app/src/org/commcare/activities/FormEntryActivity.java +++ b/app/src/org/commcare/activities/FormEntryActivity.java @@ -832,8 +832,15 @@ private void saveDataToDisk(boolean exit, boolean complete, String updatedSaveNa int formRecordId = getIntent().getIntExtra(KEY_FORM_RECORD_ID, -1); int formDefId = getIntent().getIntExtra(KEY_FORM_DEF_ID, -1); - FormSaveHelper formSaveHelper = new FormSaveHelper(formRecordId, formDefId, exit, complete, - updatedSaveName, symetricKey, FormEntryInstanceState.mFormRecordPath); + FormSaveHelper formSaveHelper; + try { + formSaveHelper = FormSaveHelper.getNewInstance(formRecordId, formDefId, exit, complete, + updatedSaveName, symetricKey, FormEntryInstanceState.mFormRecordPath); + } catch (IllegalStateException e) { + // We already have a form save in progress outside the scope of saveToDisk Task, let it do it's thing + return; + } + mSaveToDiskTask = new SaveToDiskTask(formSaveHelper, headless); if (!headless) { diff --git a/app/src/org/commcare/tasks/FormSaveHelper.java b/app/src/org/commcare/tasks/FormSaveHelper.java index 198b2b2e1..0cd0b9a81 100644 --- a/app/src/org/commcare/tasks/FormSaveHelper.java +++ b/app/src/org/commcare/tasks/FormSaveHelper.java @@ -35,6 +35,11 @@ public class FormSaveHelper { + private static FormSaveHelper singletonRunningInstance = null; + private static final Object lock = new Object(); + + private static final String TAG = FormSaveHelper.class.getSimpleName(); + private final Boolean mExitAfterSave; private final Boolean mMarkCompleted; private final int mFormRecordId; @@ -45,7 +50,7 @@ public class FormSaveHelper { private final SecretKeySpec mSymetricKey; - public FormSaveHelper(int formRecordId, int formDefId, Boolean exitAfterSave, Boolean markCompleted, + private FormSaveHelper(int formRecordId, int formDefId, Boolean exitAfterSave, Boolean markCompleted, String recordName, SecretKeySpec symetricKey, String formRecordPath) { mFormRecordId = formRecordId; mFormDefId = formDefId; @@ -56,6 +61,25 @@ public FormSaveHelper(int formRecordId, int formDefId, Boolean exitAfterSave, Bo mSymetricKey = symetricKey; } + public static FormSaveHelper getNewInstance(int formRecordId, int formDefId, Boolean exitAfterSave, Boolean markCompleted, + String recordName, SecretKeySpec symetricKey, String formRecordPath){ + synchronized (lock) { + if (singletonRunningInstance == null) { + singletonRunningInstance = new FormSaveHelper(formRecordId, formDefId, exitAfterSave, + markCompleted, recordName, symetricKey, formRecordPath); + return singletonRunningInstance; + } else { + throw new IllegalStateException("An instance of " + TAG + " already exists."); + } + } + } + + public static void clearInstance() { + synchronized (lock) { + singletonRunningInstance = null; + } + } + public ResultAndError saveForm() { long time = System.currentTimeMillis(); try { diff --git a/app/src/org/commcare/tasks/SaveToDiskTask.java b/app/src/org/commcare/tasks/SaveToDiskTask.java index 44dbe4990..47ecb3504 100644 --- a/app/src/org/commcare/tasks/SaveToDiskTask.java +++ b/app/src/org/commcare/tasks/SaveToDiskTask.java @@ -1,40 +1,10 @@ package org.commcare.tasks; -import org.commcare.CommCareApplication; import org.commcare.activities.FormEntryActivity; -import org.commcare.activities.components.ImageCaptureProcessing; -import org.commcare.android.database.app.models.FormDefRecord; -import org.commcare.android.database.user.models.FormRecord; -import org.commcare.android.logging.ForceCloseLogger; import org.commcare.interfaces.FormSavedListener; -import org.commcare.logging.XPathErrorLogger; -import org.commcare.models.database.SqlStorage; -import org.commcare.models.encryption.EncryptionIO; import org.commcare.tasks.templates.CommCareTask; -import org.commcare.util.FormMetaIndicatorUtil; -import org.commcare.util.LogTypes; -import org.commcare.utils.FileUtil; -import org.javarosa.core.io.StreamsUtil; import org.javarosa.core.model.FormDef; -import org.javarosa.core.model.FormIndex; -import org.javarosa.core.model.instance.FormInstance; -import org.javarosa.core.model.instance.TreeReference; -import org.javarosa.core.services.Logger; -import org.javarosa.core.services.locale.Localization; -import org.javarosa.core.services.transport.payload.ByteArrayPayload; -import org.javarosa.form.api.FormController; import org.javarosa.form.api.FormEntryController; -import org.javarosa.model.xform.XFormSerializingVisitor; -import org.javarosa.xform.util.XFormSerializer; -import org.javarosa.xpath.XPathException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.crypto.spec.SecretKeySpec; /** * @author Carl Hartung (carlhartung@gmail.com) @@ -85,6 +55,7 @@ protected ResultAndError doTaskBackground(Void... nothing) { protected void onPostExecute(ResultAndError result) { super.onPostExecute(result); synchronized (this) { + FormSaveHelper.clearInstance(); if (mSavedListener != null) { if (result == null) { mSavedListener.savingComplete(SaveStatus.SAVE_ERROR, "Unknown Error"); @@ -113,4 +84,9 @@ public void setFormSavedListener(FormSavedListener fsl) { } } + @Override + protected void onCancelled() { + super.onCancelled(); + FormSaveHelper.clearInstance(); + } }