SyncbaseMain now monitoring todo list collection for destroy

Previously SyncbaseMain was only monitoring the user collection for
deletions, and doing delete from that to trigger todo list collection
destroy.

However, SyncbaseTodoList was doing its delete only by todo list
collection destroy, causing an exception.

I believe we can treat either as primary, and we should be
consistent. Choosing the collection's existence as primary has the
advantage that it is less succeptible to orphaning since the user
collection is the "discovery" mechanism for the todo list collection, so
its entry there should last until we're sure the todo list collection is
gone.

There are also some unanswered questions about what collection
destruction means for other Syncbase instances sharing the
collection. If for example destruction is synced, then we need not
monitor the DELETE event on the user collection row at all (pending
solidification of the sharing scheme). If on the other hand it is not
synced, it is imperative that we react to the DELETE event on the user
collection to sync the todo list collection destroy, and in this case it
is susceptible to orphaning if the application fails to perform this
destroy successfully (e.g. early app termination).

Change-Id: I749f7d09eed4fd867296dbc93277b91445315207
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
index 11545d3..52c662d 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/MainListTracker.java
@@ -4,9 +4,10 @@
 
 package io.v.todos.persistence.syncbase;
 
+import android.support.annotation.NonNull;
 import android.util.Log;
 
-import com.google.common.base.Function;
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -27,6 +28,7 @@
 import io.v.v23.syncbase.Collection;
 import io.v.v23.syncbase.Database;
 import io.v.v23.syncbase.WatchChange;
+import io.v.v23.verror.NoExistException;
 
 /**
  * This class aggregates Todo-list watch data from Syncbase into {@link ListMetadata}.
@@ -34,8 +36,6 @@
 public class MainListTracker {
     private static final String TAG = MainListTracker.class.getSimpleName();
 
-    private final VContext mWatchContext;
-    private final Collection mList;
     private final ListEventListener<ListMetadata> mListener;
     private ListSpec mListSpec;
 
@@ -43,15 +43,14 @@
     private int mNumCompletedTasks;
     private boolean mListExistsLocally;
 
+    public final Collection collection;
     public final ListenableFuture<Void> watchFuture;
 
-    public MainListTracker(VContext vContext, Database database, String listId,
+    public MainListTracker(VContext vContext, Database database, final String listId,
                            ListEventListener<ListMetadata> listener) {
-        mList = database.getCollection(vContext, listId);
+        collection = database.getCollection(vContext, listId);
         mListener = listener;
-
-        mWatchContext = vContext.withCancel();
-        InputChannel<WatchChange> watch = database.watch(mWatchContext, mList.id(), "");
+        InputChannel<WatchChange> watch = database.watch(vContext, collection.id(), "");
         watchFuture = InputChannels.withCallback(watch, new InputChannelCallback<WatchChange>() {
             @Override
             public ListenableFuture<Void> onNext(WatchChange change) {
@@ -59,26 +58,25 @@
                 return null;
             }
         });
-    }
 
-    public ListenableFuture<Void> deleteList(VContext vContext) {
-        // The watch context has to be cancelled first or else we may run into race conditions as
-        // the collection is destroyed while the watch is still ongoing, which fails the watch with
-        // a NoExistException. Alternatively we could just ignore that exception and not bother
-        // cancelling the watch at all.
-        mWatchContext.cancel();
-        return Futures.transform(mList.destroy(vContext),
-                new Function<Void, Void>() {
-                    @Override
-                    public Void apply(@Nullable Void input) {
-                        mListener.onItemDelete(mList.id().getName());
-                        return null;
-                    }
-                });
+        // If the watch fails with NoExistException, the collection has been deleted.
+        Futures.addCallback(watchFuture, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(@Nullable Void result) {
+            }
+
+            @Override
+            public void onFailure(@NonNull Throwable t) {
+                if (t instanceof NoExistException && mListExistsLocally) {
+                    Log.d(TAG, listId + " destroyed");
+                    mListener.onItemDelete(listId);
+                }
+            }
+        });
     }
 
     public ListMetadata getListMetadata() {
-        return new ListMetadata(mList.id().getName(), mListSpec, mNumCompletedTasks,
+        return new ListMetadata(collection.id().getName(), mListSpec, mNumCompletedTasks,
                 mIsTaskCompleted.size());
     }
 
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
index 9e77ae3..b4ee310 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
@@ -5,6 +5,7 @@
 package io.v.todos.persistence.syncbase;
 
 import android.app.Activity;
+import android.support.annotation.NonNull;
 import android.util.Log;
 
 import com.google.common.util.concurrent.Futures;
@@ -37,6 +38,7 @@
 import io.v.v23.syncbase.RowRange;
 import io.v.v23.syncbase.WatchChange;
 import io.v.v23.vdl.VdlAny;
+import io.v.v23.verror.NoExistException;
 import io.v.v23.verror.VException;
 import io.v.v23.vom.VomUtil;
 
@@ -60,10 +62,12 @@
         trap(InputChannels.withCallback(watch, new InputChannelCallback<WatchChange>() {
             @Override
             public ListenableFuture<Void> onNext(WatchChange change) {
-                String listId = change.getRowName();
+                final String listId = change.getRowName();
 
                 if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
-                    trap(mTaskTrackers.remove(listId).deleteList(mVContext));
+                    // (this is idempotent)
+                    Log.d(TAG, listId + " removed from index");
+                    deleteTodoList(listId);
                 } else {
                     mIdGenerator.registerId(change.getRowName().substring(LISTS_PREFIX.length()));
 
@@ -76,7 +80,19 @@
                                 "for list " + listId);
                     }
 
-                    trap(listTracker.watchFuture);
+                    // If the watch fails with NoExistException, the collection has been deleted.
+                    Futures.addCallback(listTracker.watchFuture,
+                            new TrappingCallback<Void>(mActivity) {
+                                @Override
+                                public void onFailure(@NonNull Throwable t) {
+                                    if (t instanceof NoExistException) {
+                                        // (this is idempotent)
+                                        trap(getUserCollection().delete(mVContext, listId));
+                                    } else {
+                                        super.onFailure(t);
+                                    }
+                                }
+                            });
                 }
                 return null;
             }
@@ -101,7 +117,10 @@
 
     @Override
     public void deleteTodoList(String key) {
-        trap(getUserCollection().delete(mVContext, key));
+        MainListTracker tracker = mTaskTrackers.remove(key);
+        if (tracker != null) {
+            trap(tracker.collection.destroy(mVContext));
+        }
     }
 
     @Override