Syncbase startup and database setup
Without J8/Rx, asyncs are super unwieldy; for simplicity, just block
sequential asyncs and do everything on a worker thread.
Change-Id: If30130d6fbdb2fafbe80190c5f72be407e8a4ad1
diff --git a/app/build.gradle b/app/build.gradle
index ed30d8b..91d201b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,6 +8,7 @@
defaultConfig {
applicationId "io.v.todos"
minSdkVersion 21
+ multiDexEnabled true
targetSdkVersion 23
versionCode 1
versionName "1.0"
@@ -22,7 +23,6 @@
}
buildTypes {
release {
- minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
@@ -45,5 +45,9 @@
'com.android.support:cardview-v7:23.1.1',
'com.android.support:recyclerview-v7:23.1.1'
)
- firebaseCompile 'com.firebase:firebase-client-android:2.5.2'
+ firebaseCompile(
+ 'com.firebase:firebase-client-android:2.5.2',
+ 'com.google.guava:guava:19.0'
+ )
+ syncbaseCompile 'io.v:vanadium-android:2.0'
}
diff --git a/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java b/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
index 07a13c7..4d924be 100644
--- a/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/app/src/firebase/java/io/v/todos/persistence/PersistenceFactory.java
@@ -4,7 +4,7 @@
package io.v.todos.persistence;
-import android.content.Context;
+import android.app.Activity;
import io.v.todos.model.ListMetadata;
import io.v.todos.persistence.firebase.FirebaseMain;
@@ -14,22 +14,35 @@
private PersistenceFactory(){}
/**
- * Instantiates a persistence object that can be used to manipulate todo lists.
- *
- * @param context an Android context, usually from an Android activity or application
+ * Indicates whether {@link #getMainPersistence(Activity, ListEventListener)} may block. This
+ * can affect whether a progress indicator is shown and whether a worker thread is used.
*/
- public static MainPersistence getMainPersistence(Context context,
+ public static boolean mightGetMainPersistenceBlock() {
+ return false;
+ }
+
+ /**
+ * Instantiates a persistence object that can be used to manipulate todo lists.
+ */
+ public static MainPersistence getMainPersistence(Activity activity,
ListEventListener<ListMetadata> listener) {
- return new FirebaseMain(context, listener);
+ return new FirebaseMain(activity, listener);
+ }
+
+ /**
+ * Indicates whether {@link #getTodoListPersistence(Activity, String, TodoListListener)} may
+ * block. This can affect whether a progress indicator is shown and whether a worker thread is
+ * used.
+ */
+ public static boolean mightGetTodoListPersistenceBlock() {
+ return false;
}
/**
* Instantiates a persistence object that can be used to manipulate a todo list.
- *
- * @param context an Android context, usually from an Android activity or application
*/
- public static TodoListPersistence getTodoListPersistence(Context context, String key,
+ public static TodoListPersistence getTodoListPersistence(Activity activity, String key,
TodoListListener listener) {
- return new FirebaseTodoList(context, key, listener);
+ return new FirebaseTodoList(activity, key, listener);
}
}
diff --git a/app/src/main/java/io/v/todos/MainActivity.java b/app/src/main/java/io/v/todos/MainActivity.java
index ec9fcee..4d39028 100644
--- a/app/src/main/java/io/v/todos/MainActivity.java
+++ b/app/src/main/java/io/v/todos/MainActivity.java
@@ -32,6 +32,8 @@
* @author alexfandrianto
*/
public class MainActivity extends Activity {
+ private static final String TAG = "MainActivity";
+
private MainPersistence mPersistence;
// Snackoos are the code name for the list of todos.
@@ -46,7 +48,10 @@
@Override
protected void onDestroy() {
- mPersistence.close();
+ if (mPersistence != null) {
+ mPersistence.close();
+ mPersistence = null;
+ }
super.onDestroy();
}
@@ -98,33 +103,43 @@
}
}).attachToRecyclerView(recyclerView);
- mPersistence = PersistenceFactory.getMainPersistence(this,
- new ListEventListener<ListMetadata>() {
+ new PersistenceInitializer<MainPersistence>(this) {
@Override
- public void onItemAdd(ListMetadata item) {
- int position = snackoosList.insertInOrder(item);
+ protected MainPersistence initPersistence() throws Exception {
+ return PersistenceFactory.getMainPersistence(mActivity,
+ new ListEventListener<ListMetadata>() {
+ @Override
+ public void onItemAdd(ListMetadata item) {
+ int position = snackoosList.insertInOrder(item);
- adapter.notifyItemInserted(position);
- setEmptyVisiblity();
+ adapter.notifyItemInserted(position);
+ setEmptyVisiblity();
+ }
+
+ @Override
+ public void onItemUpdate(ListMetadata item) {
+ int start = snackoosList.findIndexByKey(item.key);
+ int end = snackoosList.updateInOrder(item);
+
+ adapter.notifyItemMoved(start, end);
+ adapter.notifyItemChanged(end);
+ }
+
+ @Override
+ public void onItemDelete(String key) {
+ int position = snackoosList.removeByKey(key);
+
+ adapter.notifyItemRemoved(position);
+ setEmptyVisiblity();
+ }
+ });
}
@Override
- public void onItemUpdate(ListMetadata item) {
- int start = snackoosList.findIndexByKey(item.key);
- int end = snackoosList.updateInOrder(item);
-
- adapter.notifyItemMoved(start, end);
- adapter.notifyItemChanged(end);
+ protected void onSuccess(MainPersistence persistence) {
+ mPersistence = persistence;
}
-
- @Override
- public void onItemDelete(String key) {
- int position = snackoosList.removeByKey(key);
-
- adapter.notifyItemRemoved(position);
- setEmptyVisiblity();
- }
- });
+ }.execute(PersistenceFactory.mightGetMainPersistenceBlock());
}
// Set the visibility based on what the adapter thinks is the visible item count.
diff --git a/app/src/main/java/io/v/todos/PersistenceInitializer.java b/app/src/main/java/io/v/todos/PersistenceInitializer.java
new file mode 100644
index 0000000..dd79802
--- /dev/null
+++ b/app/src/main/java/io/v/todos/PersistenceInitializer.java
@@ -0,0 +1,87 @@
+// Copyright 2016 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.todos;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.common.base.Throwables;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import io.v.todos.persistence.Persistence;
+
+/**
+ * Persistence initialization may or may not block. For non-blocking persistence initialization,
+ * this class simply calls {@link #initPersistence()} and
+ * {@link #onSuccess(Persistence)}/{@link #onFailure(Exception)} in a single thread. For blocking
+ * persistence initialization, it shows a {@link ProgressDialog} while initializing persistence on
+ * a worker thread. This obscures the main UI in a friendly way until persistence is ready.
+ */
+public abstract class PersistenceInitializer<T extends Persistence> {
+ private static final String TAG = PersistenceInitializer.class.getSimpleName();
+ // TODO(rosswang): https://github.com/vanadium/issues/issues/1309
+ private static final Executor sInitExecutor = Executors.newCachedThreadPool();
+
+ protected final Activity mActivity;
+
+ public PersistenceInitializer(Activity activity) {
+ mActivity = activity;
+ }
+
+ protected abstract T initPersistence() throws Exception;
+
+ protected abstract void onSuccess(T persistence);
+
+ protected void onFailure(Exception e) {
+ Toast.makeText(mActivity, R.string.err_init, Toast.LENGTH_LONG).show();
+ Log.e(TAG, Throwables.getStackTraceAsString(e));
+ mActivity.finish();
+ }
+
+ public void execute(boolean mayBlock) {
+ if (mayBlock) {
+ final ProgressDialog progress = new ProgressDialog(mActivity);
+ progress.setMessage(mActivity.getString(R.string.init_persistence));
+ progress.setCancelable(false);
+ progress.show();
+
+ new AsyncTask<Void, Void, Object>() {
+ @Override
+ protected Object doInBackground(Void... params) {
+ try {
+ return initPersistence();
+ } catch (Exception e) {
+ return e;
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void onPostExecute(Object o) {
+ if (o instanceof Exception) {
+ onFailure((Exception) o);
+ } else {
+ onSuccess((T) o);
+ }
+ progress.dismiss();
+ }
+ }.executeOnExecutor(sInitExecutor);
+ } else {
+ T persistence;
+ try {
+ persistence = initPersistence();
+ } catch (Exception e) {
+ onFailure(e);
+ return;
+ }
+ onSuccess(persistence);
+ }
+ }
+}
diff --git a/app/src/main/java/io/v/todos/TodoListActivity.java b/app/src/main/java/io/v/todos/TodoListActivity.java
index 7d22c42..ebe2e68 100644
--- a/app/src/main/java/io/v/todos/TodoListActivity.java
+++ b/app/src/main/java/io/v/todos/TodoListActivity.java
@@ -38,7 +38,7 @@
private TodoListPersistence mPersistence;
private ListSpec snackoo;
- private DataList<Task> snackoosList = new DataList<Task>();
+ private DataList<Task> snackoosList = new DataList<>();
// This adapter handle mirrors the firebase list values and generates the corresponding todo
// item View children for a list view.
@@ -50,7 +50,10 @@
@Override
protected void onDestroy() {
- mPersistence.close();
+ if (mPersistence != null) {
+ mPersistence.close();
+ mPersistence = null;
+ }
super.onDestroy();
}
@@ -59,7 +62,7 @@
setContentView(R.layout.activity_main);
Intent intent = getIntent();
- String snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
+ final String snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
setActionBar(toolbar);
@@ -89,61 +92,70 @@
}
}).attachToRecyclerView(recyclerView);
- mPersistence = PersistenceFactory.getTodoListPersistence(this, snackooKey,
- new TodoListListener() {
+ new PersistenceInitializer<TodoListPersistence>(this) {
@Override
- public void onUpdate(ListSpec value) {
- snackoo = value;
- getActionBar().setTitle(snackoo.getName());
+ protected TodoListPersistence initPersistence() throws Exception {
+ return PersistenceFactory.getTodoListPersistence(mActivity, snackooKey,
+ new TodoListListener() {
+ @Override
+ public void onUpdate(ListSpec value) {
+ snackoo = value;
+ getActionBar().setTitle(snackoo.getName());
+ }
+
+ @Override
+ public void onDelete() {
+ finish();
+ }
+
+ @Override
+ public void onUpdateShowDone(boolean showDone) {
+ mShowDone = showDone;
+ if (mShowDoneMenuItem != null) {
+ // Only interact with mShowDoneMenu if it has been inflated.
+ mShowDoneMenuItem.setChecked(showDone);
+ }
+
+ int oldSize = adapter.getItemCount();
+ adapter.setShowDone(showDone);
+ int newSize = adapter.getItemCount();
+ if (newSize > oldSize) {
+ adapter.notifyItemRangeInserted(oldSize, newSize - oldSize);
+ } else {
+ adapter.notifyItemRangeRemoved(newSize, oldSize - newSize);
+ }
+ setEmptyVisiblity();
+ }
+
+ @Override
+ public void onItemAdd(Task item) {
+ int position = snackoosList.insertInOrder(item);
+ adapter.notifyItemInserted(position);
+ setEmptyVisiblity();
+ }
+
+ @Override
+ public void onItemUpdate(Task item) {
+ int start = snackoosList.findIndexByKey(item.key);
+ int end = snackoosList.updateInOrder(item);
+ adapter.notifyItemMoved(start, end);
+ adapter.notifyItemChanged(end);
+ setEmptyVisiblity();
+ }
+
+ @Override
+ public void onItemDelete(String key) {
+ int position = snackoosList.removeByKey(key);
+ adapter.notifyItemRemoved(position);
+ setEmptyVisiblity();
+ }
+ });
}
- @Override
- public void onDelete() {
- finish();
+ protected void onSuccess(TodoListPersistence persistence) {
+ mPersistence = persistence;
}
-
- @Override
- public void onUpdateShowDone(boolean showDone) {
- mShowDone = showDone;
- if (mShowDoneMenuItem != null) {
- // Only interact with mShowDoneMenu if it has been inflated.
- mShowDoneMenuItem.setChecked(showDone);
- }
-
- int oldSize = adapter.getItemCount();
- adapter.setShowDone(showDone);
- int newSize = adapter.getItemCount();
- if (newSize > oldSize) {
- adapter.notifyItemRangeInserted(oldSize, newSize - oldSize);
- } else {
- adapter.notifyItemRangeRemoved(newSize, oldSize - newSize);
- }
- setEmptyVisiblity();
- }
-
- @Override
- public void onItemAdd(Task item) {
- int position = snackoosList.insertInOrder(item);
- adapter.notifyItemInserted(position);
- setEmptyVisiblity();
- }
-
- @Override
- public void onItemUpdate(Task item) {
- int start = snackoosList.findIndexByKey(item.key);
- int end = snackoosList.updateInOrder(item);
- adapter.notifyItemMoved(start, end);
- adapter.notifyItemChanged(end);
- setEmptyVisiblity();
- }
-
- @Override
- public void onItemDelete(String key) {
- int position = snackoosList.removeByKey(key);
- adapter.notifyItemRemoved(position);
- setEmptyVisiblity();
- }
- });
+ }.execute(PersistenceFactory.mightGetTodoListPersistenceBlock());
}
// Set the visibility based on what the adapter thinks is the visible item count.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index be63f07..a9a7282 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,4 +7,8 @@
<string name="show_done">Show Done</string>
<string name="set_button">Set</string>
<string name="add_button">Add</string>
+ <string name="init_persistence">Initializing…</string>
+ <!-- Errors -->
+ <string name="err_init">Unable to initialize persistence</string>
+ <string name="err_sync">Unable to sync</string>
</resources>
diff --git a/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java b/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
index 9fa6855..7960ed7 100644
--- a/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/app/src/syncbase/java/io/v/todos/persistence/PersistenceFactory.java
@@ -4,30 +4,47 @@
package io.v.todos.persistence;
-import android.content.Context;
+import android.app.Activity;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
import io.v.todos.model.ListMetadata;
+import io.v.todos.persistence.syncbase.SyncbaseMain;
+import io.v.v23.verror.VException;
public final class PersistenceFactory {
private PersistenceFactory(){}
/**
- * Instantiates a persistence object that can be used to manipulate todo lists.
- *
- * @param context an Android context, usually from an Android activity or application
+ * Indicates whether {@link #getMainPersistence(Activity, ListEventListener)} may block. This
+ * can affect whether a progress indicator is shown and whether a worker thread is used.
*/
- public static MainPersistence getMainPersistence(Context context,
- ListEventListener<ListMetadata> listener) {
- throw new RuntimeException("Unsupported product flavor.");
+ public static boolean mightGetMainPersistenceBlock() {
+ return !SyncbaseMain.isInitialized();
+ }
+
+ /**
+ * Instantiates a persistence object that can be used to manipulate todo lists.
+ */
+ public static MainPersistence getMainPersistence(
+ Activity activity, ListEventListener<ListMetadata> listener)
+ throws VException, SyncbaseServer.StartException {
+ return new SyncbaseMain(activity, listener);
+ }
+
+ /**
+ * Indicates whether {@link #getTodoListPersistence(Activity, String, TodoListListener)} may
+ * block. This can affect whether a progress indicator is shown and whether a worker thread is
+ * used.
+ */
+ public static boolean mightGetTodoListPersistenceBlock() {
+ return false;
}
/**
* Instantiates a persistence object that can be used to manipulate a todo list.
- *
- * @param context an Android context, usually from an Android activity or application
*/
- public static TodoListPersistence getTodoListPersistence(Context context, String key,
- TodoListListener listener) {
+ public static TodoListPersistence getTodoListPersistence(
+ Activity activity, String key, TodoListListener listener) throws VException {
throw new RuntimeException("Unsupported product flavor.");
}
}
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
new file mode 100644
index 0000000..8c7ae05
--- /dev/null
+++ b/app/src/syncbase/java/io/v/todos/persistence/syncbase/SyncbaseMain.java
@@ -0,0 +1,64 @@
+// Copyright 2016 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.todos.persistence.syncbase;
+
+import android.app.Activity;
+
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.todos.model.ListMetadata;
+import io.v.todos.model.ListSpec;
+import io.v.todos.persistence.ListEventListener;
+import io.v.todos.persistence.MainPersistence;
+import io.v.v23.VFutures;
+import io.v.v23.syncbase.Collection;
+import io.v.v23.verror.ExistException;
+import io.v.v23.verror.VException;
+
+public class SyncbaseMain extends SyncbasePersistence implements MainPersistence {
+ public static final String MAIN_COLLECTION_NAME = "userdata";
+
+ private static final Object sMainCollectionMutex = new Object();
+ private static volatile Collection sMainCollection;
+
+ public static boolean isInitialized() {
+ return sMainCollection != null;
+ }
+
+ /**
+ * This constructor blocks until the instance is ready for use.
+ */
+ public SyncbaseMain(Activity activity, ListEventListener<ListMetadata> listener)
+ throws VException, SyncbaseServer.StartException {
+ super(activity);
+
+ synchronized (sMainCollectionMutex) {
+ if (sMainCollection == null) {
+ Collection mainCollection = getDatabase()
+ .getCollection(mVContext, MAIN_COLLECTION_NAME);
+ try {
+ VFutures.sync(mainCollection.create(mVContext, null));
+ } catch (ExistException e) {
+ // This is fine.
+ }
+ sMainCollection = mainCollection;
+ }
+ }
+ }
+
+ @Override
+ public void addTodoList(ListSpec listSpec) {
+
+ }
+
+ @Override
+ public void deleteTodoList(String key) {
+
+ }
+
+ @Override
+ public void completeAllTasks(ListMetadata listMetadata) {
+
+ }
+}
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 664184b..63ecc2c 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
@@ -4,10 +4,191 @@
package io.v.todos.persistence.syncbase;
-import io.v.todos.persistence.Persistence;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.io.File;
+import java.util.concurrent.Executors;
+
+import io.v.android.libs.security.BlessingsManager;
+import io.v.android.v23.V;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.todos.persistence.Persistence;
+import io.v.v23.VFutures;
+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.Database;
+import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.verror.ExistException;
+import io.v.v23.verror.VException;
+
+/**
+ * TODO(rosswang): Move most of this to vanadium-android.
+ */
public class SyncbasePersistence implements Persistence {
+ private static final String
+ TAG = "SyncbasePersistence",
+ FILENAME = "syncbase",
+ PROXY = "proxy",
+ DATABASE = "db",
+ BLESSINGS_KEY = "blessings";
+ // BlessingPattern initialization has to be deferred until after V23 init due to native binding.
+ private static final Supplier<AccessList> OPEN_ACL = Suppliers.memoize(
+ new Supplier<AccessList>() {
+ @Override
+ public AccessList get() {
+ return new AccessList(ImmutableList.of(new BlessingPattern("...")),
+ ImmutableList.<String>of());
+ }
+ });
+
+ protected static final ListeningExecutorService sExecutor =
+ MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+
+ private static final Object sSyncbaseMutex = new Object();
+ private static VContext sVContext;
+ private static SyncbaseService sSyncbase;
+
+ private static String startSyncbaseServer(VContext vContext, Context appContext,
+ Permissions serverPermissions)
+ throws SyncbaseServer.StartException {
+ try {
+ vContext = V.withListenSpec(vContext, V.getListenSpec(vContext).withProxy(PROXY));
+ } catch (VException e) {
+ Log.w(TAG, "Unable to set up Vanadium proxy for Syncbase");
+ }
+
+ File storageRoot = new File(appContext.getFilesDir(), FILENAME);
+ storageRoot.mkdirs();
+
+ Log.i(TAG, "Starting Syncbase");
+ VContext serverContext = SyncbaseServer.withNewServer(vContext,
+ new SyncbaseServer.Params()
+ .withPermissions(serverPermissions)
+ .withStorageRootDir(storageRoot.getAbsolutePath()));
+
+ Server server = V.getServer(serverContext);
+ return "/" + server.getStatus().getEndpoints()[0];
+ }
+
+ /**
+ * Ensures that Syncbase is running. This should not be called until after the Vanadium
+ * principal has assumed blessings. The Syncbase server will run until the process is killed.
+ *
+ * @throws IllegalStateException if blessings were not attached to the principal beforehand
+ * @throws io.v.impl.google.services.syncbase.SyncbaseServer.StartException if there was an
+ * error starting the syncbase service
+ */
+ private static SyncbaseService ensureSyncbaseStarted(Context androidContext)
+ throws SyncbaseServer.StartException {
+ synchronized (sSyncbaseMutex) {
+ if (sSyncbase == null) {
+ final Context appContext = androidContext.getApplicationContext();
+ VContext singletonContext = V.init(appContext);
+ try {
+ Blessings clientBlessings = V.getPrincipal(singletonContext)
+ .blessingStore().defaultBlessings();
+ if (clientBlessings == null) {
+ throw new IllegalStateException("Blessings must be attached to the " +
+ "Vanadium principal before Syncbase initialization.");
+ }
+
+ AccessList clientAcl = new AccessList(ImmutableList.of(
+ new BlessingPattern(clientBlessings.toString())),
+ ImmutableList.<String>of());
+
+ Permissions permissions = new Permissions(ImmutableMap.of(
+ Constants.RESOLVE.getValue(), OPEN_ACL.get(),
+ Constants.READ.getValue(), clientAcl,
+ Constants.WRITE.getValue(), clientAcl,
+ Constants.ADMIN.getValue(), clientAcl));
+
+ sSyncbase = Syncbase.newService(startSyncbaseServer(
+ singletonContext, appContext, permissions));
+ } catch (SyncbaseServer.StartException | RuntimeException e) {
+ singletonContext.cancel();
+ throw e;
+ }
+ sVContext = singletonContext;
+ }
+ }
+ return sSyncbase;
+ }
+
+ private static final Object sDatabaseMutex = new Object();
+ private static Database sDatabase;
+
+ private static Database ensureDatabaseExists(Context androidContext) throws VException {
+ synchronized (sDatabaseMutex) {
+ if (sDatabase == null) {
+ final Database db = sSyncbase.getDatabase(sVContext, DATABASE, null);
+
+ try {
+ VFutures.sync(db.create(sVContext, null));
+ } catch (ExistException e) {
+ // This is fine.
+ }
+ sDatabase = db;
+ }
+ }
+ return sDatabase;
+ }
+
+ protected final Activity mActivity;
+ protected final VContext mVContext;
+
+ protected Database getDatabase() {
+ return sDatabase;
+ }
+
+ /**
+ * This constructor is blocking for simplicity.
+ */
+ public SyncbasePersistence(final Activity activity)
+ throws VException, SyncbaseServer.StartException {
+ mActivity = activity;
+ mVContext = V.init(activity);
+
+ // We might not actually have to seek blessings each time, but getBlessings does not
+ // block if we already have blessings and this has better-behaved lifecycle
+ // implications than trying to seek blessings in the static code.
+ final SettableFuture<ListenableFuture<Blessings>> blessings = SettableFuture.create();
+ if (activity.getMainLooper().getThread() == Thread.currentThread()) {
+ blessings.set(BlessingsManager.getBlessings(mVContext, activity, BLESSINGS_KEY, true));
+ } else {
+ new Handler(activity.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ blessings.set(BlessingsManager.getBlessings(
+ mVContext, activity, BLESSINGS_KEY, true));
+ }
+ });
+ }
+ VFutures.sync(Futures.dereference(blessings));
+ ensureSyncbaseStarted(activity);
+ ensureDatabaseExists(activity);
+ }
+
@Override
public void close() {
+ mVContext.cancel();
}
}