syncslides: Syncbase initialization.
Initialize syncbase and do a better job of handling errors along the
way.
Change-Id: I359f86afb8838aa7060be8e8dd97cabcde15bea4
diff --git a/android/app/src/main/java/io/v/syncslides/DeckChooserActivity.java b/android/app/src/main/java/io/v/syncslides/DeckChooserActivity.java
index dba60e0..c866ec1 100644
--- a/android/app/src/main/java/io/v/syncslides/DeckChooserActivity.java
+++ b/android/app/src/main/java/io/v/syncslides/DeckChooserActivity.java
@@ -11,6 +11,7 @@
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
+import android.widget.Toast;
import io.v.syncslides.V23;
@@ -29,7 +30,13 @@
super.onCreate(savedInstanceState);
// Immediately initialize V23, possibly sending user to the
// AccountManager to get blessings.
- V23.Singleton.get().init(getApplicationContext(), this);
+ try {
+ V23.Singleton.get().init(getApplicationContext(), this);
+ } catch (InitException e) {
+ // TODO(kash): Start a to-be-written SettingsActivity that makes it possible
+ // to wipe the state of syncbase and/or blessings.
+ handleError("Failed to init", e);
+ }
setContentView(R.layout.activity_deck_chooser);
@@ -79,12 +86,21 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- Log.d(TAG, "onActivityResult");
- if (V23.Singleton.get().onActivityResult(
- getApplicationContext(), requestCode, resultCode, data)) {
- Log.d(TAG, "did the v23 result");
- return;
+ try {
+ if (V23.Singleton.get().onActivityResult(
+ getApplicationContext(), requestCode, resultCode, data)) {
+ return;
+ }
+ } catch (InitException e) {
+ // TODO(kash): Start a to-be-written SettingsActivity that makes it possible
+ // to wipe the state of syncbase and/or blessings.
+ handleError("Failed onActivityResult initialization", e);
}
// Any other activity results would be handled here.
}
+
+ private void handleError(String msg, Throwable throwable) {
+ Log.e(TAG, msg + ": " + Log.getStackTraceString(throwable));
+ Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
+ }
}
diff --git a/android/app/src/main/java/io/v/syncslides/InitException.java b/android/app/src/main/java/io/v/syncslides/InitException.java
new file mode 100644
index 0000000..3fd2038
--- /dev/null
+++ b/android/app/src/main/java/io/v/syncslides/InitException.java
@@ -0,0 +1,18 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncslides;
+
+/**
+ * Thrown when initialization fails.
+ */
+public class InitException extends Exception {
+ public InitException(Throwable throwable) {
+ super(throwable);
+ }
+
+ public InitException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+}
diff --git a/android/app/src/main/java/io/v/syncslides/V23.java b/android/app/src/main/java/io/v/syncslides/V23.java
index 1a8ab37..4c9b5d7 100644
--- a/android/app/src/main/java/io/v/syncslides/V23.java
+++ b/android/app/src/main/java/io/v/syncslides/V23.java
@@ -13,6 +13,7 @@
import io.v.android.v23.V;
import io.v.android.v23.services.blessing.BlessingCreationException;
import io.v.android.v23.services.blessing.BlessingService;
+import io.v.syncslides.db.DB;
import io.v.v23.context.VContext;
import io.v.v23.security.BlessingPattern;
import io.v.v23.security.Blessings;
@@ -53,7 +54,7 @@
private V23() {
}
- public void init(Context context, Activity activity) {
+ public void init(Context context, Activity activity) throws InitException {
if (mBlessings != null) {
return;
}
@@ -72,16 +73,18 @@
/**
* To be called from an Activity's onActivityResult method, e.g.
- * public void onActivityResult(
- * int requestCode, int resultCode, Intent data) {
- * if (V23.Singleton.get().onActivityResult(
- * getApplicationContext(), requestCode, resultCode, data)) {
- * return;
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ * try {
+ * if (V23.Singleton.get().onActivityResult(
+ * getApplicationContext(), requestCode, resultCode, data)) {
+ * return;
+ * }
+ * } catch (InitException e) {
+ * // Handle the error, possibly by resetting blessings and syncbase.
* }
- * }
*/
public boolean onActivityResult(
- Context context, int requestCode, int resultCode, Intent data) {
+ Context context, int requestCode, int resultCode, Intent data) throws InitException {
if (requestCode != BLESSING_REQUEST) {
return false;
}
@@ -91,37 +94,65 @@
Blessings blessings = (Blessings) VomUtil.decode(blessingsVom, Blessings.class);
BlessingsManager.addBlessings(mContext, blessings);
configurePrincipal(blessings);
-// DB.Singleton.get(androidCtx).init();
} catch (BlessingCreationException e) {
- throw new IllegalStateException(e);
+ throw new InitException(e);
} catch (VException e) {
- throw new IllegalStateException(e);
+ throw new InitException(e);
}
return true;
}
- private Blessings loadBlessings() {
+ private Blessings loadBlessings() throws InitException {
try {
// See if there are blessings stored in shared preferences.
return BlessingsManager.getBlessings(mContext);
} catch (VException e) {
- Log.w(TAG, "Cannot get blessings from prefs: " + e.getMessage());
+ throw new InitException(e);
}
- return null;
}
- private void configurePrincipal(Blessings blessings) {
+ private void configurePrincipal(Blessings blessings) throws InitException {
try {
VPrincipal p = V.getPrincipal(mVContext);
p.blessingStore().setDefaultBlessings(blessings);
p.blessingStore().set(blessings, new BlessingPattern("..."));
VSecurity.addToRoots(p, blessings);
mBlessings = blessings;
+ DB.Singleton.get().init(mContext);
} catch (VException e) {
- Log.e(TAG, String.format(
- "Couldn't set local blessing %s: %s", blessings, e.getMessage()));
+ throw new InitException(
+ String.format("Couldn't set local blessing %s", blessings), e);
}
}
+ /**
+ * v23 operations that require a blessing (almost everything) will fail if
+ * attempted before this is true.
+ *
+ * The simplest usage is 1) There are no blessings. 2) An activity starts
+ * and calls V23Manager.init. 2) init notices there are no blessings and
+ * calls startActivityForResult 3) meanwhile, the activity and/or its
+ * components still run, but can test isBlessed before attempting anything
+ * requiring blessings. The activity will soon be re-initialized anyway. 4)
+ * user kicked over into 'account manager', gets a blessing, and the
+ * activity is restarted, this time with isBlessed == true.
+ */
+ public boolean isBlessed() {
+ return mBlessings != null;
+ }
+
+ /**
+ * Returns the blessings for this process.
+ */
+ public Blessings getBlessings() {
+ return mBlessings;
+ }
+
+ /**
+ * Returns the Vanadium context for this process.
+ */
+ public VContext getVContext() {
+ return mVContext;
+ }
}
diff --git a/android/app/src/main/java/io/v/syncslides/db/DB.java b/android/app/src/main/java/io/v/syncslides/db/DB.java
new file mode 100644
index 0000000..8281671
--- /dev/null
+++ b/android/app/src/main/java/io/v/syncslides/db/DB.java
@@ -0,0 +1,39 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncslides.db;
+
+import android.content.Context;
+
+import io.v.syncslides.InitException;
+import io.v.syncslides.model.Deck;
+import io.v.syncslides.model.DynamicList;
+
+/**
+ * Provides high-level methods for getting and setting the state of SyncSlides.
+ * It is an interface instead of a concrete class to make testing easier.
+ */
+public interface DB {
+ class Singleton {
+ private static volatile DB instance;
+
+ public static DB get() {
+ DB result = instance;
+ if (instance == null) {
+ synchronized (Singleton.class) {
+ result = instance;
+ if (result == null) {
+ instance = result = new SyncbaseDB();
+ }
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Perform initialization steps.
+ */
+ void init(Context context) throws InitException;
+}
diff --git a/android/app/src/main/java/io/v/syncslides/db/SyncbaseDB.java b/android/app/src/main/java/io/v/syncslides/db/SyncbaseDB.java
new file mode 100644
index 0000000..8ad5fce
--- /dev/null
+++ b/android/app/src/main/java/io/v/syncslides/db/SyncbaseDB.java
@@ -0,0 +1,146 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncslides.db;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Arrays;
+
+import io.v.android.v23.V;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.syncslides.InitException;
+import io.v.syncslides.V23;
+import io.v.syncslides.model.Deck;
+import io.v.syncslides.model.DynamicList;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.Server;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.Blessings;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Constants;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.SyncbaseApp;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.syncbase.nosql.Database;
+import io.v.v23.syncbase.nosql.Table;
+import io.v.v23.verror.VException;
+
+public class SyncbaseDB implements DB {
+ private static final String TAG = "SyncbaseDB";
+ private static final String SYNCBASE_APP = "syncslides";
+ private static final String SYNCBASE_DB = "syncslides";
+ private static final String DECKS_TABLE = "Decks";
+ private static final String NOTES_TABLE = "Notes";
+ static final String PRESENTATIONS_TABLE = "Presentations";
+ static final String CURRENT_SLIDE = "CurrentSlide";
+ static final String QUESTIONS = "questions";
+ private static final String SYNCGROUP_PRESENTATION_DESCRIPTION = "Live Presentation";
+
+ private boolean mInitialized = false;
+ private Handler mHandler;
+ private Permissions mPermissions;
+ private Context mContext;
+ private VContext mVContext;
+ private Server mSyncbaseServer;
+ private Database mDB;
+
+ // Singleton.
+ SyncbaseDB() {
+ }
+
+ @Override
+ public void init(Context context) throws InitException {
+ if (mInitialized) {
+ return;
+ }
+ mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
+
+ // If blessings aren't in place, the fragment that called this
+ // initialization may continue to load and use DB, but nothing will
+ // work so DB methods should return noop values. It's assumed that
+ // the calling fragment will send the user to the AccountManager,
+ // accept blessings on return, then re-call this init.
+ if (!V23.Singleton.get().isBlessed()) {
+ Log.d(TAG, "no blessings.");
+ return;
+ }
+ mVContext = V23.Singleton.get().getVContext();
+ setupSyncbase();
+ }
+
+ // TODO(kash): Run this in an AsyncTask so it doesn't block the UI.
+ private void setupSyncbase() throws InitException {
+ Blessings blessings = V23.Singleton.get().getBlessings();
+ AccessList everyoneAcl = new AccessList(
+ ImmutableList.of(new BlessingPattern("...")), ImmutableList.<String>of());
+ AccessList justMeAcl = new AccessList(
+ ImmutableList.of(new BlessingPattern(blessings.toString())),
+ ImmutableList.<String>of());
+
+ mPermissions = new Permissions(ImmutableMap.of(
+ Constants.RESOLVE.getValue(), everyoneAcl,
+ Constants.READ.getValue(), justMeAcl,
+ Constants.WRITE.getValue(), justMeAcl,
+ Constants.ADMIN.getValue(), justMeAcl));
+
+ // Prepare the syncbase storage directory.
+ File storageDir = new File(mContext.getFilesDir(), "syncbase");
+ storageDir.mkdirs();
+
+ try {
+ String id = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.ANDROID_ID);
+ mVContext = SyncbaseServer.withNewServer(mVContext, new SyncbaseServer.Params()
+ .withPermissions(mPermissions)
+ // TODO(kash): Mount it!
+ //.withName(V23Manager.syncName(id))
+ .withStorageRootDir(storageDir.getAbsolutePath()));
+ } catch (SyncbaseServer.StartException e) {
+ throw new InitException("Couldn't start syncbase server", e);
+ }
+ try {
+ mSyncbaseServer = V.getServer(mVContext);
+ Log.i(TAG, "Endpoints: " + Arrays.toString(mSyncbaseServer.getStatus().getEndpoints()));
+ String serverName = "/" + mSyncbaseServer.getStatus().getEndpoints()[0];
+
+ // Now that we've started Syncbase, set up our connections to it.
+ SyncbaseService service = Syncbase.newService(serverName);
+ SyncbaseApp app = service.getApp(SYNCBASE_APP);
+ if (!app.exists(mVContext)) {
+ app.create(mVContext, mPermissions);
+ }
+ mDB = app.getNoSqlDatabase(SYNCBASE_DB, null);
+ if (!mDB.exists(mVContext)) {
+ mDB.create(mVContext, mPermissions);
+ }
+ Table decks = mDB.getTable(DECKS_TABLE);
+ if (!decks.exists(mVContext)) {
+ decks.create(mVContext, mPermissions);
+ }
+ Table notes = mDB.getTable(NOTES_TABLE);
+ if (!notes.exists(mVContext)) {
+ notes.create(mVContext, mPermissions);
+ }
+ Table presentations = mDB.getTable(PRESENTATIONS_TABLE);
+ if (!presentations.exists(mVContext)) {
+ presentations.create(mVContext, mPermissions);
+ }
+ //importDecks();
+ } catch (VException e) {
+ throw new InitException("Couldn't setup syncbase service", e);
+ }
+ mInitialized = true;
+ }
+}