croupier: Modify the Croupier Schema

This makes way for the next step in game creation.

We need a procedure where we have space to invite players to a game.
Upon accepting those players must also form a new syncgroup for the
game. These two actions are highly related and cross the settings and
games tables. Thus, room must be made (prefix-shifting/table-combining),
which is what has occurred in this CL.

The game log now occurs at games/<game-id>/log -> all of the log data
The settings data now is all in the same table.
settings/personal holds the user's id
settings/users/<user_id>/settings holds the user's JSON settings.

Further, we are a little saner about keeping track of the player's userID.

Space has been made since:
games/<game-id> -> other stuff can be placed here for game formation
settings/users/<user_id> -> other stuff can be placed here for invitations

Change-Id: I5ffe78deb7ad9a126d993c8b84ae4364edda0f20
diff --git a/lib/logic/hearts/hearts_log.part.dart b/lib/logic/hearts/hearts_log.part.dart
index 9b92f2d..52685e4 100644
--- a/lib/logic/hearts/hearts_log.part.dart
+++ b/lib/logic/hearts/hearts_log.part.dart
@@ -8,7 +8,8 @@
   LogWriter logWriter;
 
   HeartsLog() {
-    logWriter = new LogWriter(handleSyncUpdate, [0, 1, 2, 3]);
+    // TODO(alexfandrianto): The Game ID needs to be part of this constructor.
+    logWriter = new LogWriter(handleSyncUpdate, [0, 1, 2, 3], "<game_id>/log");
   }
 
   @override
diff --git a/lib/logic/solitaire/solitaire_log.part.dart b/lib/logic/solitaire/solitaire_log.part.dart
index 5551d31..973941c 100644
--- a/lib/logic/solitaire/solitaire_log.part.dart
+++ b/lib/logic/solitaire/solitaire_log.part.dart
@@ -8,7 +8,8 @@
   LogWriter logWriter;
 
   SolitaireLog() {
-    logWriter = new LogWriter(handleSyncUpdate, [0]);
+    // TODO(alexfandrianto): The Game ID needs to be part of this constructor.
+    logWriter = new LogWriter(handleSyncUpdate, [0], "<game_id>/log");
   }
 
   @override
diff --git a/lib/src/mocks/log_writer.dart b/lib/src/mocks/log_writer.dart
index e5e753a..c48fc1c 100644
--- a/lib/src/mocks/log_writer.dart
+++ b/lib/src/mocks/log_writer.dart
@@ -11,11 +11,12 @@
 class LogWriter {
   final updateCallbackT updateCallback;
   final List<int> users;
+  final String logPrefix; // This can be completely ignored.
 
   bool inProposalMode = false;
   int associatedUser;
 
-  LogWriter(this.updateCallback, this.users);
+  LogWriter(this.updateCallback, this.users, this.logPrefix);
 
   Map<String, String> _data = new Map<String, String>();
 
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
index eefaef9..4940fe8 100644
--- a/lib/src/syncbase/log_writer.dart
+++ b/lib/src/syncbase/log_writer.dart
@@ -29,14 +29,28 @@
 enum SimulLevel { TURN_BASED, INDEPENDENT, DEPENDENT }
 
 class LogWriter {
+  // This callback is called on each watch update, passing the key and value.
   final util.updateCallbackT updateCallback;
+
+  // The users that we should look for when coming to a proposal consensus.
   final List<int> users;
+
+  // The CroupierClient manages the creation of tables/dbs/syncgroups.
   final CroupierClient _cc;
 
+  // Affects read/write/watch locations of the log writer.
+  final String logPrefix;
+
+  // An internal boolean that should be set to true when watching and reset to
+  // false once watch should be turned off.
   bool _watching = false;
 
+  // When a proposal is made (or received), this flag is set to true.
+  // Once a consensus has been reached, this is set to false again.
   bool inProposalMode = false;
   Map<String, String> proposalsKnown; // Only updated via watch.
+
+  // The associated user helps in the production of unique keys.
   int _associatedUser;
   int get associatedUser => _associatedUser;
   void set associatedUser(int other) {
@@ -45,25 +59,28 @@
     _associatedUser = other;
   }
 
-  LogWriter(this.updateCallback, this.users) : _cc = new CroupierClient() {
+  // This holds a reference to the syncbase table we're writing to.
+  SyncbaseTable tb;
+  static final String tbName = util.tableNameGames;
+
+  // The LogWriter takes a callback for watch updates, the list of users, and
+  // the logPrefix to write at on table.
+  LogWriter(this.updateCallback, this.users, this.logPrefix)
+      : _cc = new CroupierClient() {
     _prepareLog();
   }
 
-  int seq = 0;
-  SyncbaseTable tb;
-  String sendMsg, recvMsg, putStr, getStr;
-
   Future _prepareLog() async {
     if (tb != null) {
       return; // Then we're already prepared.
     }
 
     SyncbaseNoSqlDatabase db = await _cc.createDatabase();
-    tb = await _cc.createTable(db, util.tableNameLog);
+    tb = await _cc.createTable(db, tbName);
 
     // Start to watch the stream.
     Stream<WatchChange> watchStream =
-        db.watch(util.tableNameLog, '', await db.getResumeMarker());
+        db.watch(tbName, this.logPrefix, await db.getResumeMarker());
     _startWatch(watchStream); // Don't wait for this future.
   }
 
@@ -81,10 +98,10 @@
         break;
       }
 
-      assert(wc.tableName == util.tableNameLog);
+      assert(wc.tableName == tbName);
       util.log('Watch Key: ${wc.rowKey}');
       util.log('Watch Value ${UTF8.decode(wc.valueBytes)}');
-      String key = wc.rowKey;
+      String key = wc.rowKey.replaceFirst("${this.logPrefix}/", "");
       String value;
       switch (wc.changeType) {
         case WatchChangeTypes.put:
@@ -145,14 +162,14 @@
 
   // Helper that writes data to the "store" and calls the update callback.
   Future _writeData(String key, String value) async {
-    var row = tb.row(key);
+    var row = tb.row("${this.logPrefix}/${key}");
     await row.put(UTF8.encode(value));
   }
 
   /*
   // _readData could be helpful eventually, but it's not needed yet.
   Future<String> _readData(String key) async {
-    var row = tb.row(key);
+    var row = tb.row("${this.logPrefix}/${key}");
     if (!(await row.exists())) {
       print("${key} did not exist");
       return null;
@@ -226,7 +243,7 @@
   }
 
   String _proposalKey(int user) {
-    return "proposal${user}";
+    return "proposal/${user}";
   }
 
   Future<bool> _checkIsProposalDone() async {
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 2dea144..367ee76 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -24,55 +24,52 @@
 
 import 'package:discovery/discovery.dart' as discovery;
 import 'package:syncbase/syncbase_client.dart' as sc;
-//import 'package:syncbase/src/naming/util.dart' as naming;
 
 class SettingsManager {
   final util.updateCallbackT updateCallback;
   final CroupierClient _cc;
-
-  static final String discoverySettingsKey = "settings";
-
   sc.SyncbaseTable tb;
-  sc.SyncbaseTable tbUser;
+
+  static const String _discoverySettingsKey = "settings";
+  static const String _personalKey = "personal";
+  static const String _settingsWatchSyncPrefix = "users";
 
   SettingsManager([this.updateCallback]) : _cc = new CroupierClient();
 
+  String _settingsDataKey(int userID) {
+    return "${_settingsWatchSyncPrefix}/${userID}/settings";
+  }
+
   Future _prepareSettingsTable() async {
-    if (tb != null && tbUser != null) {
+    if (tb != null) {
       return; // Then we're already prepared.
     }
 
     sc.SyncbaseNoSqlDatabase db = await _cc.createDatabase();
     tb = await _cc.createTable(db, util.tableNameSettings);
-    tbUser = await _cc.createTable(db, util.tableNameSettingsUser);
 
     // Start to watch the stream for the shared settings table.
-    Stream<sc.WatchChange> watchStream =
-        db.watch(util.tableNameSettings, '', await db.getResumeMarker());
+    Stream<sc.WatchChange> watchStream = db.watch(util.tableNameSettings,
+        _settingsWatchSyncPrefix, await db.getResumeMarker());
     _startWatch(watchStream); // Don't wait for this future.
     _loadSettings(tb); // Don't wait for this future.
   }
 
   // Guaranteed to be called when the program starts.
   // If no Croupier Settings exist, then random ones are created.
-  Future<String> load([int userID]) async {
+  Future<String> load() async {
     util.log('SettingsManager.load');
     await _prepareSettingsTable();
 
-    String result;
+    int userID = await _getUserID();
     if (userID == null) {
-      result = await _tryReadData(tbUser, "settings");
-    } else {
-      result = await _tryReadData(tb, "${userID}");
-    }
-    if (result == null) {
       CroupierSettings settings = new CroupierSettings.random();
       String jsonStr = settings.toJSONString();
       await this.save(settings.userID, jsonStr);
       return jsonStr;
+    } else {
+      return await _tryReadData(tb, this._settingsDataKey(userID));
     }
-
-    return result;
   }
 
   Future<String> _tryReadData(sc.SyncbaseTable st, String rowkey) async {
@@ -84,14 +81,15 @@
     return UTF8.decode(await row.get());
   }
 
-  // Since only the current user is allowed to save, we should also save to the
-  // user's personal settings as well.
+  // Note: only the current user is allowed to save settings.
+  // This means we can also save their user id.
+  // All other settings will be synced instead.
   Future save(int userID, String jsonString) async {
     util.log('SettingsManager.save');
     await _prepareSettingsTable();
 
-    await tbUser.row("settings").put(UTF8.encode(jsonString));
-    await tb.row("${userID}").put(UTF8.encode(jsonString));
+    await tb.row(_personalKey).put(UTF8.encode("${userID}"));
+    await tb.row(this._settingsDataKey(userID)).put(UTF8.encode(jsonString));
   }
 
   // This watch method ensures that any changes are propagated to the caller.
@@ -125,18 +123,23 @@
 
   // Best called after load(), to ensure that there are settings in the table.
   Future createSyncgroup() async {
-    CroupierSettings cs = await _getSettings();
+    int id = await _getUserID();
 
     _cc.createSyncgroup(
         _cc.makeSyncgroupName(await _syncSuffix()), util.tableNameSettings,
-        prefix: "${cs.userID}");
+        prefix: this._settingsDataKey(id));
   }
 
   // When starting the settings manager, there may be settings already in the
   // store. Make sure to load those.
   Future _loadSettings(sc.SyncbaseTable tb) async {
-    tb.scan(new sc.RowRange.prefix('')).forEach((sc.KeyValue kv) {
-      this.updateCallback(kv.key, UTF8.decode(kv.value));
+    tb
+        .scan(new sc.RowRange.prefix(_settingsWatchSyncPrefix))
+        .forEach((sc.KeyValue kv) {
+      if (kv.key.endsWith("/settings")) {
+        // Then we can process the value as if it were settings data.
+        this.updateCallback(kv.key, UTF8.decode(kv.value));
+      }
     });
   }
 
@@ -147,36 +150,39 @@
   // Someone who is creating a game should scan for players who wish to join.
   Future scanSettings() async {
     SettingsScanHandler ssh = new SettingsScanHandler(_cc);
-    _cc.discoveryClient.scan(discoverySettingsKey, "CroupierSettings", ssh);
+    _cc.discoveryClient.scan(_discoverySettingsKey, "CroupierSettings", ssh);
   }
 
   void stopScanSettings() {
-    _cc.discoveryClient.stopScan(discoverySettingsKey);
+    _cc.discoveryClient.stopScan(_discoverySettingsKey);
   }
 
   // Someone who wants to join a game should advertise their presence.
   Future advertiseSettings() async {
     String suffix = await _syncSuffix();
     _cc.discoveryClient.advertise(
-        discoverySettingsKey,
+        _discoverySettingsKey,
         DiscoveryClient.serviceMaker(
             interfaceName: "CroupierSettings",
             addrs: <String>[_cc.makeSyncgroupName(suffix)]));
   }
 
   void stopAdvertiseSettings() {
-    _cc.discoveryClient.stopAdvertise(discoverySettingsKey);
+    _cc.discoveryClient.stopAdvertise(_discoverySettingsKey);
   }
 
-  Future<CroupierSettings> _getSettings() async {
-    String jsonSettings = await load();
-    return new CroupierSettings.fromJSONString(jsonSettings);
+  Future<int> _getUserID() async {
+    String result = await _tryReadData(tb, _personalKey);
+    if (result == null) {
+      return null;
+    }
+    return int.parse(result);
   }
 
   Future<String> _syncSuffix() async {
-    CroupierSettings cs = await _getSettings();
+    int id = await _getUserID();
 
-    return "${util.sgSuffix}${cs.userID}";
+    return "${util.sgSuffix}${id}";
   }
 }
 
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index 02f1160..456e083 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -6,9 +6,8 @@
 
 const appName = 'app';
 const dbName = 'db';
-const tableNameLog = 'table';
+const tableNameGames = 'games';
 const tableNameSettings = 'table_settings';
-const tableNameSettingsUser = 'table_settings_personal';
 
 // TODO(alexfandrianto): This may need to be the global mount table with a
 // proxy. Otherwise, it will be difficult for other users to run.