Todos: Use the new invitations API to reduce the number of scans / advertisements.

Change-Id: Ica497cdbcd0fa8da90d07b05ad221062ba360e4b
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 c107310..de345b3 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
@@ -120,7 +120,6 @@
         Permissions permissions = Util.filterPermissionsByTags(
                 computePermissionsFromBlessings(getPersonalBlessings()),
                 io.v.v23.services.syncbase.Constants.ALL_COLLECTION_TAGS);
-
         Futures.addCallback(listCollection.create(getVContext(), permissions),
                 new SyncTrappingCallback<Void>() {
                     @Override
diff --git a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
index 8622abc..88b0bfb 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbasePersistence.java
@@ -89,6 +89,10 @@
  * TODO(rosswang): Move most of this to vanadium-android.
  */
 public class SyncbasePersistence implements Persistence {
+    public static final String LISTS_PREFIX = "lists_";
+    public static final String
+        LIST_COLLECTION_SYNCGROUP_PREFIX = "list_";
+
     private static final String
             TAG = "SyncbasePersistence",
             FILENAME = "syncbase",
@@ -96,11 +100,9 @@
             DATABASE = "db",
             BLESSINGS_KEY = "blessings",
             USER_COLLECTION_SYNCGROUP_SUFFIX = "sg_",
-            LIST_COLLECTION_SYNCGROUP_SUFFIX = "list_",
             DEFAULT_APP_BLESSING_STRING = "dev.v" +
                     ".io:o:608941808256-43vtfndets79kf5hac8ieujto8837660" +
                     ".apps.googleusercontent.com";
-    protected static final String LISTS_PREFIX = "lists_";
     protected static final long
             SHORT_TIMEOUT = 2500,
             MEMBER_TIMER_DELAY = 100,
@@ -423,7 +425,7 @@
     }
 
     protected static String computeListSyncgroupName(String listId) {
-        return LIST_COLLECTION_SYNCGROUP_SUFFIX + listId;
+        return LIST_COLLECTION_SYNCGROUP_PREFIX + listId;
     }
 
     private static String BLESSING_NAME_SEPARATOR = "___";
@@ -565,7 +567,7 @@
         // TODO(alexfandrianto): If the cloud is dependent on me, then we must do this too.
         // VFutures.sync(ensureCloudDatabaseExists); // must finish before syncgroup setup
         ensureUserSyncgroupExists();
-        Sharing.initDiscovery(); // requires that db and collection exist
+        Sharing.initDiscovery(sDatabase); // requires that db and collection exist
         sInitialized = true;
     }
 
diff --git a/app/src/syncbase/java/io/v/todos/sharing/Sharing.java b/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
index 69f8a74..ac033a3 100644
--- a/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
+++ b/app/src/syncbase/java/io/v/todos/sharing/Sharing.java
@@ -33,6 +33,8 @@
 import io.v.v23.security.BlessingPattern;
 import io.v.v23.services.syncbase.Id;
 import io.v.v23.syncbase.ChangeType;
+import io.v.v23.syncbase.Database;
+import io.v.v23.syncbase.Invite;
 import io.v.v23.syncbase.Syncbase;
 import io.v.v23.syncbase.WatchChange;
 import io.v.v23.verror.VException;
@@ -42,18 +44,15 @@
     }
 
     private static final String TAG = "SHARING";
-    private static final String OWNER_KEY = "owner";
     private static final Object sDiscoveryMutex = new Object();
     private static Discovery sDiscovery;
     private static VContext sScanContext;
-    private static VContext sAdvertiseContext;
-    private final static Map<String, VContext> sAdContextMap = new HashMap<>();
 
     public static Discovery getDiscovery() {
         return sDiscovery;
     }
 
-    public static void initDiscovery() throws VException {
+    public static void initDiscovery(Database db) throws VException {
         synchronized (sDiscoveryMutex) {
             if (sDiscovery == null) {
                 sDiscovery = V.newDiscovery(SyncbasePersistence.getAppVContext());
@@ -61,8 +60,7 @@
                 // Rely on the neighborhood fragment to initialize presence advertisement.
                 NeighborhoodFragment.initSharePresence();
 
-                sScanContext = initScanForInvites();
-                sAdvertiseContext = initAdvertiseLists();
+                sScanContext = initScanForInvites(db);
             }
         }
     }
@@ -71,8 +69,6 @@
     public static void stopDiscovery() {
         synchronized (sDiscoveryMutex) {
             sScanContext.cancel();
-            sAdvertiseContext.cancel();
-            sAdContextMap.clear();
         }
     }
 
@@ -85,57 +81,29 @@
         return getRootInterface() + ".presence2";
     }
 
-    public static String getInvitationInterface() {
-        return getRootInterface() + ".invitation2";
-    }
-
     /**
      * Starts a scanner seeking advertisements that invite this user to a todo list. When an invite
      * is found, the app will automatically accept it.
      */
-    public static VContext initScanForInvites()
+    public static VContext initScanForInvites(Database db)
             throws VException {
         VContext vContext = SyncbasePersistence.getAppVContext().withCancel();
         try {
-            ListenableFuture<Void> scan = InputChannels.withCallback(
-                    Sharing.getDiscovery().scan(vContext,
-                            "v.InterfaceName = \"" + Sharing.getInvitationInterface() + "\""),
-                    new InputChannelCallback<Update>() {
-                        @Override
-                        public ListenableFuture<Void> onNext(Update result) {
-                            final String listName = Iterables.getOnlyElement(result.getAddresses());
-                            if (listName == null) {
-                                return null;
-                            }
-                            String owner = result.getAttribute(OWNER_KEY);
-                            Log.d("SHARING", "Noticed advertised list: " + listName + " by: " +
-                                    owner);
-
-                            // TODO(alexfandrianto): Remove hack.
-                            // https://github.com/vanadium/issues/issues/1328
-                            if (result.getAttribute(SyncbasePersistence
-                                    .getPersonalBlessingsString()) == null) {
-                                Log.d(TAG, "...but the ad was not meant for this user.");
-                                return null; // ignore; this isn't meant for us
-                            }
-
-                            // Never mind about losses, just handle found advertisements.
-                            if (!result.isLost()) {
-                                Log.d(TAG, "...and will accept it.");
-
-                                SyncbasePersistence.acceptSharedTodoList(new Id(owner, listName));
-                            }
-                            return null;
-                        }
-                    });
-            Futures.addCallback(scan, new FutureCallback<Void>() {
+            db.listenForInvites(vContext, new Database.InviteHandler() {
                 @Override
-                public void onSuccess(@Nullable Void result) {
-                }
-
-                @Override
-                public void onFailure(Throwable t) {
-                    handleScanListsError(t);
+                public void handleInvite(Invite invite) {
+                    String prefix = SyncbasePersistence.LIST_COLLECTION_SYNCGROUP_PREFIX +
+                        SyncbasePersistence.LISTS_PREFIX;
+                    String name = invite.getSyncgroupId().getName();
+                    if (!name.startsWith(prefix)) {
+                        // Not actually a Todo List.
+                        return;
+                    }
+                    Log.d(TAG, "Accepting todo list invite: " + invite.getSyncgroupId().toString());
+                    String blessing = invite.getSyncgroupId().getBlessing();
+                    Id listId = new Id(blessing, name.substring(
+                        SyncbasePersistence.LIST_COLLECTION_SYNCGROUP_PREFIX.length()));
+                    SyncbasePersistence.acceptSharedTodoList(listId);
                 }
             });
         } catch (VException e) {
@@ -147,132 +115,4 @@
     private static void handleScanListsError(Throwable t) {
         SyncbasePersistence.getAppErrorReporter().onError(R.string.err_scan_lists, t);
     }
-
-    /**
-     * Creates advertisements based on the todo lists this user has created thus far and those that
-     * are created in the future. The advertisements will need to be targeted to the users that have
-     * been invited to the list.
-     *
-     * @return
-     * @throws VException
-     */
-    public static VContext initAdvertiseLists()
-            throws VException {
-        final VContext vContext = SyncbasePersistence.getAppVContext().withCancel();
-
-        // Prepare a watch on top of the userdata collection to determine which todo lists need to
-        // be tracked by this application.
-        SyncbasePersistence.watchUserCollection(new InputChannelCallback<WatchChange>() {
-            @Override
-            public ListenableFuture<Void> onNext(WatchChange change) {
-                try {
-                    final String listIdStr = change.getRowName();
-                    final Id listId = SyncbasePersistence.convertStringToId(listIdStr);
-
-                    if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
-                        VContext ctx = sAdContextMap.remove(listIdStr);
-                        if (ctx != null) { // TODO(alexfandrianto): ctx might be null if ad failed?
-                            ctx.cancel(); // Stop advertising the list; it's been deleted.
-                        }
-                    } else {
-                        final String owner = listId.getBlessing();
-                        if (!owner.equals(SyncbasePersistence.getPersonalBlessingsString())) {
-                            return Futures.immediateFuture((Void) null);
-                        }
-
-                        // We should probably start to advertise this collection and check its spec.
-                        SyncbasePersistence.watchSharedTo(listId, new Function<List<BlessingPattern>,
-                                Void>() {
-                            @Override
-                            public Void apply(List<BlessingPattern> patterns) {
-                                // Make a copy of the patterns list that excludes the cloud and this
-                                // user's blessings.
-                                List<BlessingPattern> filteredPatterns = new ArrayList<>();
-                                for (BlessingPattern pattern : patterns) {
-                                    String pStr = pattern.toString();
-                                    if (pStr.equals(SyncbasePersistence.getPersonalBlessingsString()) ||
-                                            pStr.equals(SyncbasePersistence.CLOUD_BLESSING)) {
-                                        continue;
-                                    }
-                                    filteredPatterns.add(pattern);
-                                }
-
-                                // Advertise to the remaining patterns.
-                                if (filteredPatterns.size() > 0) {
-                                    Log.d(TAG, "Must advertise for " + listIdStr + " to " +
-                                            filteredPatterns.toString());
-                                    advertiseList(vContext, listId, filteredPatterns);
-                                }
-                                return null;
-                            }
-                        });
-                    }
-                } catch (Exception e) {
-                    Log.w(TAG, "Error during watch handle", e);
-                }
-                return null;
-            }
-        });
-        return vContext;
-    }
-
-    /**
-     * Advertises that this list is available to this set of people. Cancels the old advertisement
-     * if one exists. Only called by initAdvertiseLists.
-     *
-     * @param baseContext The context for all advertisements
-     * @param listId      The list to be advertised
-     * @param patterns    Blessings that the advertisement should target
-     */
-    private static void advertiseList(VContext baseContext, Id listId, List<BlessingPattern>
-            patterns) {
-        if (baseContext.isCanceled()) {
-            Log.w(TAG, "Base context was canceled; cannot advertise");
-            return;
-        }
-        // Swap out the ad context...
-        String key = SyncbasePersistence.convertIdToString(listId);
-        VContext oldAdContext = sAdContextMap.remove(key);
-        if (oldAdContext != null) {
-            oldAdContext.cancel();
-        }
-        VContext newAdContext = baseContext.withCancel();
-        sAdContextMap.put(key, newAdContext);
-
-
-        try {
-            Advertisement ad = new Advertisement();
-            ad.setInterfaceName(Sharing.getInvitationInterface());
-            ad.getAddresses().add(listId.getName());
-            ad.getAttributes().put(OWNER_KEY, listId.getBlessing());
-
-            // TODO(alexfandrianto): Remove hack. https://github.com/vanadium/issues/issues/1328
-            for (BlessingPattern pattern : patterns) {
-                ad.getAttributes().put(pattern.toString(), "");
-            }
-
-            Futures.addCallback(Sharing.getDiscovery().advertise(sAdvertiseContext, ad,
-                    // TODO(alexfandrianto): Crypto crash if I use patterns instead of null.
-                    // https://github.com/vanadium/issues/issues/1328 and
-                    // https://github.com/vanadium/issues/issues/1331
-                    null),
-                    //patterns),
-                    new FutureCallback<Void>() {
-                        @Override
-                        public void onSuccess(@android.support.annotation.Nullable Void result) {
-                        }
-
-                        @Override
-                        public void onFailure(@NonNull Throwable t) {
-                            handleAdvertiseListError(t);
-                        }
-                    });
-        } catch (VException e) {
-            handleAdvertiseListError(e);
-        }
-    }
-
-    private static void handleAdvertiseListError(Throwable t) {
-        SyncbasePersistence.getAppErrorReporter().onError(R.string.err_advertise_list, t);
-    }
 }