Get syncgroup working cross-device.

Change-Id: Id1764d0a95ba0f5ae393a0d419f8334162e9a493
diff --git a/Makefile b/Makefile
index 4a29353..79eb5c9 100644
--- a/Makefile
+++ b/Makefile
@@ -5,12 +5,13 @@
 include ../shared/mojo.mk
 
 # Flags for Syncbase service running as Mojo service.
-SYNCBASED_ADDR := 127.0.0.1:4002
-V23_MOJO_FLAGS := --v=0 --v23.tcp.address=$(SYNCBASED_ADDR) --v23.permissions.literal={\"Admin\":{\"In\":[\"...\"]},\"Write\":{\"In\":[\"...\"]},\"Read\":{\"In\":[\"...\"]},\"Resolve\":{\"In\":[\"...\"]},\"Debug\":{\"In\":[\"...\"]}}
+V23_MOJO_FLAGS := --v=0
 
 # MOUNTTABLE_ADDR := 127.0.0.1:4001
 ifdef MOUNTTABLE_ADDR
-	V23_MOJO_FLAGS += --name=syncbase_mojo --v23.namespace.root=/$(MOUNTTABLE_ADDR)
+	# TODO(nlacasse): Get your email address out of here!
+	MOUNT_NAME := users/nlacasse@google.com/syncbase_mojo
+	V23_MOJO_FLAGS += --name=$(MOUNT_NAME) --v23.proxy=proxy --v23.namespace.root=$(MOUNTTABLE_ADDR)
 endif
 
 ifdef ANDROID
@@ -25,13 +26,15 @@
 	# directory inside APP_HOME_DIR.)  We set syncbase root-dir inside
 	# APP_HOME_DIR for the same reason.
 	APP_HOME_DIR = /data/data/org.chromium.mojo.shell/app_home
-	V23_MOJO_FLAGS += --logtostderr=true --root-dir=$(APP_HOME_DIR)/syncbase_data
+	ANDROID_CREDS_DIR := /sdcard/v23creds
+	V23_MOJO_FLAGS += --logtostderr=true --root-dir=$(APP_HOME_DIR)/syncbase_data --v23.credentials=$(ANDROID_CREDS_DIR)
 else
 	ETHER_BUILD_DIR := $(PWD)/gen/mojo/linux_amd64
 
 	THIRD_PARTY_LIBS := $(JIRI_ROOT)/third_party/cout/linux_amd64
 
-	V23_MOJO_FLAGS += --root-dir=$(PWD)/tmp/syncbase_data
+	SYNCBASE_ROOT_DIR := $(PWD)/tmp/syncbase_data
+	V23_MOJO_FLAGS += --root-dir=$(SYNCBASE_ROOT_DIR) --v23.credentials=$(PWD)/creds
 endif
 
 # NOTE(nlacasse): Running Go Mojo services requires passing the
@@ -58,7 +61,7 @@
 	touch $@
 
 # Mints credentials.
-creds: bin
+creds: | bin
 	./bin/principal seekblessings --v23.credentials creds
 	touch $@
 
@@ -106,8 +109,11 @@
 	cd dart && pub upgrade
 
 .PHONY: run-syncbase-example
-run-syncbase-example: $(ETHER_BUILD_DIR)/syncbase_server.mojo dart/packages dart/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart | syncbase-env-check
-	$(call MOJO_RUN,https://mojo.v.io/syncbase_example.dart)
+run-syncbase-example: $(ETHER_BUILD_DIR)/syncbase_server.mojo dart/packages dart/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart | syncbase-env-check creds
+ifdef ANDROID
+	adb push -p $(PWD)/creds $(ANDROID_CREDS_DIR)
+endif
+	$(call MOJO_RUN,"https://mojo.v.io/syncbase_example.dart $(MOUNTTABLE_ADDR)")
 
 .PHONY: test
 test: test-unit test-integration
@@ -118,6 +124,14 @@
 
 .PHONY: test-integration
 test-integration: dart/packages $(ETHER_BUILD_DIR)/syncbase_server.mojo gen-mojom | syncbase-env-check
+ifdef MOUNTTABLE_ADDR
+	$(error please unset MOUNTTABLE_ADDR before running the tests)
+endif
+	# Delete the 'creds' dir to make sure we are running with in-memory
+	# credentials.  Otherwise tests time out.
+	# TODO(nlacasse): Figure out why tests time out with dev.v.io credentials.
+	# Maybe caveat validation?
+	rm -rf $(PWD)/creds
 	# NOTE(nlacasse): The "tests" argument must come before the "MOJO_SHELL_FLAGS" flags,
 	# otherwise mojo_test's argument parser gets confused and exits with an error.
 	$(MOJO_DIR)/src/mojo/devtools/common/mojo_test tests --config-file $(PWD)/mojoconfig --shell-path $(MOJO_SHELL_PATH) $(MOJO_ANDROID_FLAGS) $(MOJO_SHELL_FLAGS)
@@ -135,7 +149,7 @@
 # TODO(aghassemi): Why does mojo generate dart-pkg and mojom dirs?
 .PHONY: clean
 clean:
-	rm -rf bin gen tmp
+	rm -rf bin creds gen tmp
 	rm -rf dart/lib/gen/dart-pkg
 	rm -rf dart/lib/gen/mojom
 
diff --git a/dart/bin/syncbase_example.dart b/dart/bin/syncbase_example.dart
index 02886a5..03bde77 100644
--- a/dart/bin/syncbase_example.dart
+++ b/dart/bin/syncbase_example.dart
@@ -3,19 +3,158 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+import 'dart:async';
+import 'dart:math' show Random;
+import 'dart:convert' show UTF8;
+
 import 'package:ether/initialized_application.dart' show InitializedApplication;
-import 'package:ether/syncbase_client.dart' show SyncbaseClient;
+import 'package:ether/src/naming/util.dart' as naming;
+import 'package:ether/syncbase_client.dart' as sb;
 
 main(List args) async {
-  InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
-  await app.initialized;
+  // mojo_shell does not print a stack trace when the dart program crashes.
+  // Hence, we must wrap everything in a try/catch if we want to see the
+  // errors. :(
+  try {
+    InitializedApplication app = new InitializedApplication.fromHandle(args[0]);
+    await app.initialized;
 
-  SyncbaseClient c = new SyncbaseClient(
-      app.connectToService, 'https://mojo.v.io/syncbase_server.mojo');
+    // Application arguments are in app.args.
+    // app.args[0] = url of this service.  Rest of arguments follow.
+    if (app.args.length < 2 || app.args[1] == null || app.args[1] == '') {
+      throw 'mount table address must be first argument';
+    }
 
-  bool exists = await c.app('foo').exists();
-  print('app(foo).exists(): $exists');
+    String mtAddr = app.args[1];
 
-  await c.close();
-  await app.close();
+    sb.SyncbaseClient c = new sb.SyncbaseClient(
+        app.connectToService, 'https://mojo.v.io/syncbase_server.mojo');
+
+    sb.SyncbaseApp sbApp = await createApp(c, 'testapp');
+    sb.SyncbaseNoSqlDatabase sbDb = await createDb(sbApp, 'testdb');
+    sb.SyncbaseTable sbTable = await createTable(sbDb, 'testtable');
+    await joinOrCreateSyncGroup(sbDb, mtAddr, sbTable.name, 'testsg');
+
+    startWatch(sbDb, sbTable);
+    startPuts(sbTable);
+
+    // Wait forever.
+    await new Completer().future;
+
+    // Looks like forever came and went.  Might as well clean up after
+    // ourselves...
+    await c.close();
+    await app.close();
+  } catch (e) {
+    print('ERROR in main()');
+    print(e);
+  }
+}
+
+startWatch(db, table) async {
+  try {
+    var s = db.watch(table.name, '', await db.getResumeMarker());
+    s.forEach((change) {
+      print('GOT CHANGE: ${change.rowName} - ${UTF8.decode(change.valueBytes)} - ${change.fromSync}');
+    });
+  } catch (e) {
+    print('ERROR in startWatch()');
+    print(e);
+  }
+}
+
+var r = new Random();
+
+startPuts(table) async {
+  try {
+    var key = r.nextInt(100000000);
+    var val = r.nextInt(100000000);
+
+    var row = table.row('k$key');
+    print('PUTTING k$key');
+    await row.put(UTF8.encode('$val'));
+  } catch (e) {
+    print('ERROR in startPuts()');
+    print(e);
+  }
+
+  await new Future.delayed(new Duration(seconds:5));
+  startPuts(table);
+}
+
+String openPermsJson =
+    '{"Admin":{"In":["..."]},"Write":{"In":["..."]},"Read":{"In":["..."]},"Resolve":{"In":["..."]},"Debug":{"In":["..."]}}';
+sb.Perms openPerms = sb.SyncbaseClient.perms(openPermsJson);
+
+Future<sb.SyncbaseApp> createApp(sb.SyncbaseClient c, String name) async {
+  var app = c.app(name);
+  var exists = await app.exists();
+  if (exists) {
+    print('app exists, rolling with it');
+    return app;
+  }
+  print('app does not exist, creating it');
+  await app.create(openPerms);
+  return app;
+}
+
+Future<sb.SyncbaseNoSqlDatabase> createDb(sb.SyncbaseApp app, String name) async {
+  var db = app.noSqlDatabase(name);
+  var exists = await db.exists();
+  if (exists) {
+    print('db exists, rolling with it');
+    return db;
+  }
+  print('db does not exist, creating it');
+  await db.create(openPerms);
+  return db;
+}
+
+Future<sb.SyncbaseTable> createTable(sb.SyncbaseNoSqlDatabase db, String name) async {
+  var table = db.table(name);
+  var exists = await table.exists();
+  if (exists) {
+    print('table exists, rolling with it');
+    return table;
+  }
+  print('table does not exist, creating it');
+  await table.create(openPerms);
+  return table;
+}
+
+Future<sb.SyncbaseSyncGroup> joinOrCreateSyncGroup(sb.SyncbaseNoSqlDatabase db, String mtAddr, String tableName, String name) async {
+  // TODO(nlacasse): Get your email address out of here!  Figure out a way to
+  // get the mounttable name and path to this part of the code.
+  var mtName = naming.join(mtAddr, 'users/nlacasse@google.com');
+  // TODO(nlacasse): Make this %%sync thing a constant.
+  var sgPrefix = naming.join(mtName, 'syncbase_mojo/%%sync');
+  var sgName = naming.join(sgPrefix, 'testsg');
+  var sg = db.syncGroup(sgName);
+
+  print('SGNAME = $sgName');
+
+  var myInfo = sb.SyncbaseClient.syncGroupMemberInfo(syncPriority: 3);
+
+  try {
+    print('trying to join syncgroup');
+    await sg.join(myInfo);
+    print('syncgroup join success');
+  } catch (e) {
+    // Syncgroup does not exist.
+    print('syncgroup does not exist, creating it');
+
+    var sgSpec = sb.SyncbaseClient.syncGroupSpec(
+      description: 'test sync group',
+      perms: openPerms,
+      // Sync the entire table.
+      prefixes: ['$tableName:'],
+      mountTables: [mtName]
+    );
+
+    print('SGSPEC = $sgSpec');
+
+    await sg.create(sgSpec, myInfo);
+  }
+
+  return sg;
 }
diff --git a/dart/lib/initialized_application.dart b/dart/lib/initialized_application.dart
index 7d4b32d..797de2d 100644
--- a/dart/lib/initialized_application.dart
+++ b/dart/lib/initialized_application.dart
@@ -12,13 +12,17 @@
 // InitializedApplication is an Application with a future 'initialized' that is
 // resolved after the 'initialize' method finishes.
 class InitializedApplication extends Application {
-  final _initializeCompleter = new Completer();
+  final Completer _initializeCompleter = new Completer();
   Future get initialized => _initializeCompleter.future;
 
+  List<String> _args;
+  List<String> get args => _args;
+
   InitializedApplication.fromHandle(int handle)
       : super.fromHandle(new MojoHandle(handle));
 
   void initialize(List<String> args, String url) {
+    _args = args;
     _initializeCompleter.complete();
   }
 }