package org.commcare.android.tasks; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; import org.commcare.android.database.SqlStorage; import org.commcare.android.database.user.models.FormRecord; import org.commcare.android.database.user.models.SessionStateDescriptor; import org.commcare.android.models.AndroidSessionWrapper; import org.commcare.android.util.AndroidCommCarePlatform; import org.commcare.suite.model.Text; import android.content.Context; import android.os.AsyncTask; import android.util.Pair; /** * @author ctsims * */ public class FormRecordLoaderTask extends AsyncTask<FormRecord, Pair<Integer, ArrayList<String>>, Integer> { private Hashtable<String,String> descriptorCache; private SqlStorage<SessionStateDescriptor> descriptorStorage; private AndroidCommCarePlatform platform; private Hashtable<Integer, String[]> searchCache; private Context context; private FormRecordLoadListener listener; //These are all synchronized together private Queue<FormRecord> priorityQueue; private HashSet<Integer> loaded; private Hashtable<String,Text> formNames; public FormRecordLoaderTask(Context c, SqlStorage<SessionStateDescriptor> descriptorStorage, AndroidCommCarePlatform platform) { this(c, descriptorStorage, null, platform); } public FormRecordLoaderTask(Context c, SqlStorage<SessionStateDescriptor> descriptorStorage, Hashtable<String,String> descriptorCache, AndroidCommCarePlatform platform) { this.context = c; this.descriptorStorage = descriptorStorage; this.descriptorCache = descriptorCache; this.platform = platform; } public FormRecordLoaderTask spawn() { FormRecordLoaderTask task = new FormRecordLoaderTask(context, descriptorStorage, descriptorCache, platform); task.setListener(listener); return task; } public void init(Hashtable<Integer, String[]> searchCache, Hashtable<String,Text> formNames) { this.searchCache = searchCache; if(descriptorCache == null) { descriptorCache = new Hashtable<String,String>(); } priorityQueue = new LinkedList<FormRecord>(); loaded = new HashSet<Integer>(); this.formNames = formNames; } public void setListener(FormRecordLoadListener listener) { this.listener = listener; } /* * (non-Javadoc) * @see android.os.AsyncTask#doInBackground(java.lang.Object[]) */ @Override protected Integer doInBackground(FormRecord... params) { int progress = 0; int target = params.length; while(progress < target && !isCancelled()) { FormRecord current = null; synchronized(priorityQueue) { //If we have one to do immediately, grab it if(!priorityQueue.isEmpty()) { current = priorityQueue.poll(); loaded.add(current.getID()); //Don't increment progress yet, we'll do so //when we get to this record later. } //If we don't need to jump the queue, grab the next one. if(current == null) { current = params[progress++]; //If we already loaded this record (due to priority), //we don't need to go through this if(loaded.contains(current.getID())) { continue; } else { loaded.add(current.getID()); } } } //Otherwise, let's get this record ready. ArrayList<String> cache = new ArrayList<String>(); //Get the date in a searchable format. cache.add(android.text.format.DateUtils.formatDateTime(context, current.lastModified().getTime(), android.text.format.DateUtils.FORMAT_NO_MONTH_DAY | android.text.format.DateUtils.FORMAT_NO_YEAR).toLowerCase()); //Grab our record hash SessionStateDescriptor ssd = null; try { ssd = descriptorStorage.getRecordForValue(SessionStateDescriptor.META_FORM_RECORD_ID, current.getID()); } catch(NoSuchElementException nsee) { //s'all good } String dataTitle = ""; if(ssd != null) { String descriptor = ssd.getSessionDescriptor(); if(!descriptorCache.containsKey(descriptor)) { AndroidSessionWrapper asw = new AndroidSessionWrapper(platform); asw.loadFromStateDescription(ssd); try{ dataTitle = asw.getTitle(); } catch(RuntimeException e){ dataTitle = "[Unavailable]"; } dataTitle = dataTitle == null ? "" : dataTitle; descriptorCache.put(descriptor, dataTitle); } else { dataTitle = descriptorCache.get(descriptor); } } cache.add(dataTitle); if(formNames.containsKey(current.getFormNamespace())) { Text name = formNames.get(current.getFormNamespace()); cache.add(name.evaluate()); } //Notify anyhting waiting on this record this.publishProgress(new Pair<Integer, ArrayList<String>>(current.getID(), cache)); } return 1; } /* (non-Javadoc) * @see android.os.AsyncTask#onPostExecute(java.lang.Object) */ @Override protected void onPostExecute(Integer result) { super.onPostExecute(result); if(listener != null) { listener.notifyLoaded(); } //free up everything except the cache, which we might use later. SqlStorage<SessionStateDescriptor> descriptorStorage = null; AndroidCommCarePlatform platform = null; Hashtable<Integer, String[]> searchCache = null; Context context = null; FormRecordLoadListener listener = null; //These are all synchronized together Queue<FormRecord> priorityQueue = null; HashSet<Integer> loaded = null; } /* (non-Javadoc) * @see android.os.AsyncTask#onProgressUpdate(Progress[]) */ @Override protected void onProgressUpdate(Pair<Integer, ArrayList<String>>... values) { super.onProgressUpdate(values); String[] vals = new String[values[0].second.size()]; for(int i = 0 ; i < vals.length ; ++i) { vals[i] = values[0].second.get(i); } this.searchCache.put(values[0].first, vals); if(listener != null) { listener.notifyPriorityLoaded(values[0].first, loaded.contains(values[0].first)); } } public boolean registerPriority(FormRecord record) { synchronized(priorityQueue) { if(loaded.contains(record.getID())) { return false; } //Otherwise, if we already have it in the queue, just move along else if(priorityQueue.contains(record)) { return true; } else { priorityQueue.add(record); return true; } } } }