package org.commcare.tasks; import org.commcare.CommCareApp; import org.commcare.CommCareApplication; import org.commcare.logging.AndroidLogger; import org.commcare.models.database.SqlStorage; import org.commcare.android.database.user.models.FormRecord; import org.javarosa.core.services.Logger; import org.joda.time.DateTime; import java.util.Vector; /** * Remove saved forms that have passed their validity date from the device. If * the user launches the saved forms list activity while this is running they * should be blocked until it completes as a matter of not allowing loading * forms that might be in the process of being purged. * * @author Phillip Mates (pmates@dimagi.com). */ public class PurgeStaleArchivedFormsTask extends SingletonTask<Void, Void, Void> { private static final String DAYS_TO_RETAIN_SAVED_FORMS_KEY = "cc-days-form-retain"; public static final int PURGE_STALE_ARCHIVED_FORMS_TASK_ID = 1283; private static PurgeStaleArchivedFormsTask singletonRunningInstance = null; private static final Object lock = new Object(); private PurgeStaleArchivedFormsTask() { TAG = PurgeStaleArchivedFormsTask.class.getSimpleName(); } public static PurgeStaleArchivedFormsTask getRunningInstance() { synchronized (lock) { if (singletonRunningInstance != null && singletonRunningInstance.getStatus() == Status.RUNNING) { return singletonRunningInstance; } return null; } } public static void launchPurgeTask() { synchronized (lock) { if (singletonRunningInstance == null) { singletonRunningInstance = new PurgeStaleArchivedFormsTask(); singletonRunningInstance.executeParallel(); } } } @Override protected Void doInBackground(Void... params) { CommCareApp app = CommCareApplication.instance().getCurrentApp(); performArchivedFormPurge(app); return null; } @Override public void clearTaskInstance() { synchronized (lock) { singletonRunningInstance = null; } } /** * Purge saved forms from device that have surpassed the validity date set * by the app * * @param ccApp Used to get the saved form validity date property. */ private static void performArchivedFormPurge(CommCareApp ccApp) { int daysSavedFormIsValidFor = getArchivedFormsValidityInDays(ccApp); if (daysSavedFormIsValidFor == -1) { return; } DateTime lastValidDate = getLastValidArchivedFormDate(daysSavedFormIsValidFor); Vector<Integer> toPurge = getSavedFormsToPurge(lastValidDate); for (int recordId : toPurge) { FormRecord beingDeleted = CommCareApplication.instance().getUserStorage(FormRecord.class).read(recordId); beingDeleted.logPendingDeletion(TAG, "it is a saved form that has surpassed the validity date set by the app"); FormRecordCleanupTask.wipeRecord(CommCareApplication.instance(), recordId); } } /** * Read the validity range for archived forms out of app preferences. * * @return how long archived forms should kept on the phone. -1 if saved * forms should be kept indefinitely */ public static int getArchivedFormsValidityInDays(CommCareApp ccApp) { int daysForReview = -1; String daysToPurge = ccApp.getAppPreferences().getString(DAYS_TO_RETAIN_SAVED_FORMS_KEY, "-1"); try { daysForReview = Integer.parseInt(daysToPurge); } catch (NumberFormatException nfe) { Logger.log(AndroidLogger.TYPE_ERROR_CONFIG_STRUCTURE, "Invalid days to purge: " + daysToPurge); } return daysForReview; } private static DateTime getLastValidArchivedFormDate(int daysForReview) { return DateTime.now().minusDays(daysForReview); } /** * @return List of form record ids that correspond to forms that have * passed the saved form validity date range */ public static Vector<Integer> getSavedFormsToPurge(DateTime lastValidDate) { Vector<Integer> toPurge = new Vector<>(); SqlStorage<FormRecord> formStorage = CommCareApplication.instance().getUserStorage(FormRecord.class); String currentAppId = CommCareApplication.instance().getCurrentApp().getAppRecord().getApplicationId(); Vector<Integer> savedFormsForThisApp = formStorage.getIDsForValues( new String[]{FormRecord.META_STATUS, FormRecord.META_APP_ID}, new Object[]{FormRecord.STATUS_SAVED, currentAppId}); for (int id : savedFormsForThisApp) { String dateAsString = formStorage.getMetaDataFieldForRecord(id, FormRecord.META_LAST_MODIFIED); long timeSinceEpoch; try { timeSinceEpoch = Long.valueOf(dateAsString); } catch (NumberFormatException e) { Logger.log(AndroidLogger.SOFT_ASSERT, "Unable to parse modified date of form record: " + dateAsString); toPurge.add(id); continue; } DateTime modifiedDate = new DateTime(timeSinceEpoch); if (modifiedDate.isBefore(lastValidDate)) { toPurge.add(id); } } return toPurge; } }