package ninja.ugly.prevail.loader; import android.content.AsyncTaskLoader; import android.content.Context; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.util.Log; import com.google.common.base.Optional; import ninja.ugly.prevail.chunk.QueryResult; import ninja.ugly.prevail.datamodel.DataModel; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.concurrent.ExecutionException; public abstract class ChunkLoader<K, V> extends AsyncTaskLoader<QueryResult<V>> { private static final String TAG = ChunkLoader.class.getSimpleName(); private final Optional<String> mSegment; K mKey; QueryResult<V> mResults; private DataModel mDataModel; private CancellationSignal mCancellationSignal; public ChunkLoader(Context context, DataModel dataModel, String segment, K key) { super(context); mDataModel = dataModel; mKey = key; mSegment = segment == null ? Optional.<String>absent() : Optional.of(segment); } public ChunkLoader(final Context context, final DataModel dataModel, final K key) { this(context, dataModel, null, key); } @Override public QueryResult<V> loadInBackground() { synchronized (this) { if (isLoadInBackgroundCanceled()) { throw new OperationCanceledException(); } mCancellationSignal = new CancellationSignal(); } List<QueryResult<Object>> queryResults = null; try { if (mSegment.isPresent()) { queryResults = mDataModel.query(mSegment.get(), mKey).get(); } else { queryResults = mDataModel.query(mKey).get(); } } catch (InterruptedException e) { Log.e(TAG, "Exception querying " + mKey, e); } catch (ExecutionException e) { Log.e(TAG, "Exception querying " + mKey, e); } finally { synchronized (this) { mCancellationSignal = null; } } return (QueryResult<V>) queryResults.get(0); } @Override public void deliverResult(QueryResult<V> results) { if (isReset()) { // An async query came in while the loader is stopped close(mResults); return; } QueryResult<V> oldResults = mResults; mResults = results; if (isStarted()) { super.deliverResult(results); } if (oldResults != results) { close(oldResults); } } /** * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks * will be called on the UI thread. If a previous load has been completed and is still valid * the result may be passed to the callbacks immediately. * <p/> * Must be called from the UI thread */ @Override protected void onStartLoading() { if (mResults != null) { deliverResult(mResults); } if (takeContentChanged() || mResults == null) { forceLoad(); } } /** * Must be called from the UI thread */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } @Override public void onCanceled(QueryResult<V> result) { close(result); } private void close(final QueryResult<V> result) { if (result != null && !result.isClosed()) { try { result.close(); } catch (IOException e) { Log.d(TAG, "Exception closing old results", e); } } } @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); close(mResults); mResults = null; } @Override public void cancelLoadInBackground() { super.cancelLoadInBackground(); synchronized (this) { if (mCancellationSignal != null) { mCancellationSignal.cancel(); } } } @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); writer.print(prefix); writer.print("mKey="); writer.println(mKey); writer.print(prefix); writer.print("mSegment="); writer.println(mSegment); writer.print(prefix); writer.print("mResults="); writer.println(mResults); } }