syncbase java: start implementing high-level API in terms of low-level
Change-Id: I9eb753bfdf363ccd8056c322c27514a47fad83a7
diff --git a/syncbase/build.gradle b/syncbase/build.gradle
index 5130e92..33c0281 100644
--- a/syncbase/build.gradle
+++ b/syncbase/build.gradle
@@ -13,11 +13,31 @@
}
}
-apply plugin: 'java'
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.3"
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'junit:junit:4.12'
+ testCompile 'junit:junit:4.12'
+ compile 'com.android.support:appcompat-v7:23.4.0'
+ compile 'io.v:vanadium-android:2.1.3'
}
task clean(type: Delete) {
diff --git a/syncbase/src/main/AndroidManifest.xml b/syncbase/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..36846f6
--- /dev/null
+++ b/syncbase/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest package="io.v.syncbase">
+
+</manifest>
diff --git a/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java b/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
index 9237191..910a32a 100644
--- a/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
+++ b/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
@@ -8,26 +8,26 @@
public class BatchDatabase implements DatabaseHandle {
public Id getId() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public Collection collection(String name, CollectionOptions opts) {
- return null;
+ throw new RuntimeException("Not implemented");
}
public Collection getCollection(Id id) {
- return null;
+ throw new RuntimeException("Not implemented");
}
public Iterator<Collection> getCollections() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public void commit() {
-
+ throw new RuntimeException("Not implemented");
}
public void abort() {
-
+ throw new RuntimeException("Not implemented");
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Collection.java b/syncbase/src/main/java/io/v/syncbase/Collection.java
index 9f9062e..b68e295 100644
--- a/syncbase/src/main/java/io/v/syncbase/Collection.java
+++ b/syncbase/src/main/java/io/v/syncbase/Collection.java
@@ -5,40 +5,47 @@
package io.v.syncbase;
public class Collection {
+ private final io.v.v23.syncbase.Collection mCxImpl;
+
+ protected Collection(io.v.v23.syncbase.Collection cxImpl) {
+ // TODO(sadovsky): Add create-if-not-exists code.
+ mCxImpl = cxImpl;
+ }
+
public Id getId() {
- return null;
+ return new Id(mCxImpl.id());
}
// Shortcut for Database.getSyncgroup(c.getId()), helpful for the common case of one syncgroup
// per collection.
public Syncgroup getSyncgroup() {
- return null;
+ throw new RuntimeException("Not implemented");
}
// TODO(sadovsky): Maybe add scan API, if developers aren't satisfied with watch.
public <T> T get(String key) {
- return null;
+ throw new RuntimeException("Not implemented");
}
public boolean exists(String key) {
- return false;
+ throw new RuntimeException("Not implemented");
}
public <T> void put(String key, T value) {
-
+ throw new RuntimeException("Not implemented");
}
public void delete(String key) {
-
+ throw new RuntimeException("Not implemented");
}
// FOR ADVANCED USERS. The following methods manipulate the AccessList for this collection, but
// not for associated syncgroups.
public AccessList getAccessList() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public void updateAccessList(AccessList delta) {
-
+ throw new RuntimeException("Not implemented");
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Database.java b/syncbase/src/main/java/io/v/syncbase/Database.java
index 880b123..c83492e5 100644
--- a/syncbase/src/main/java/io/v/syncbase/Database.java
+++ b/syncbase/src/main/java/io/v/syncbase/Database.java
@@ -7,20 +7,28 @@
import java.util.Iterator;
public class Database implements DatabaseHandle {
+ private final io.v.v23.syncbase.Database mDbImpl;
+
+ protected Database(io.v.v23.syncbase.Database dbImpl) {
+ mDbImpl = dbImpl;
+ }
+
public Id getId() {
- return null;
+ return new Id(mDbImpl.id());
}
public Collection collection(String name, CollectionOptions opts) {
- return null;
+ return new Collection(mDbImpl.getCollection(new io.v.v23.services.syncbase.Id(Syncbase.getPersonalBlessingString(), name)));
}
public Collection getCollection(Id id) {
- return null;
+ return new Collection(mDbImpl.getCollection(id.toVId()));
}
public Iterator<Collection> getCollections() {
- return null;
+ // FIXME: Convert ListenableFuture<List<Id>> to Iterator<Collection>.
+ mDbImpl.listCollections(Syncbase.getVContext());
+ throw new RuntimeException("Not implemented");
}
class SyncgroupOptions {
@@ -30,15 +38,15 @@
// FOR ADVANCED USERS. Creates syncgroup and adds it to the user's "userdata" collection, as
// needed. Idempotent.
public Syncgroup syncgroup(String name, Collection[] collections, SyncgroupOptions opts) {
- return null;
+ throw new RuntimeException("Not implemented");
}
public Syncgroup getSyncgroup(Id id) {
- return null;
+ throw new RuntimeException("Not implemented");
}
public Iterator<Syncgroup> getSyncgroups() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public class AddSyncgroupInviteHandlerOptions {
@@ -47,15 +55,15 @@
// Notifies 'h' of any existing syncgroup invites, and of all subsequent new invites.
public void addSyncgroupInviteHandler(SyncgroupInviteHandler h, AddSyncgroupInviteHandlerOptions opts) {
-
+ throw new RuntimeException("Not implemented");
}
public void removeSyncgroupInviteHandler(SyncgroupInviteHandler h) {
-
+ throw new RuntimeException("Not implemented");
}
public void removeSyncgroupInviteHandlers() {
-
+ throw new RuntimeException("Not implemented");
}
// Joins syncgroup and adds it to the user's "userdata" collection, as needed.
@@ -63,17 +71,17 @@
// "ignore" methods to the SyncgroupInvite class, or should we treat it as a POJO?
// TODO(sadovsky): Make this method async.
public Syncgroup acceptSyncgroupInvite(SyncgroupInvite invite) {
- return null;
+ throw new RuntimeException("Not implemented");
}
// Records that the user has ignored this invite, such that it's never surfaced again.
// Note: This will be one of the last things we implement.
public void ignoreSyncgroupInvite(SyncgroupInvite invite) {
-
+ throw new RuntimeException("Not implemented");
}
public BatchDatabase beginBatch() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public class AddWatchChangeHandlerOptions {
@@ -85,14 +93,14 @@
// Note: Eventually we'll add a watch variant that takes a query, where the query can be
// constructed using some sort of query builder API.
public void addWatchChangeHandler(WatchChangeHandler h, AddWatchChangeHandlerOptions opts) {
-
+ throw new RuntimeException("Not implemented");
}
public void removeWatchChangeHandler(WatchChangeHandler h) {
-
+ throw new RuntimeException("Not implemented");
}
public void removeAllWatchChangeHandlers() {
-
+ throw new RuntimeException("Not implemented");
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Id.java b/syncbase/src/main/java/io/v/syncbase/Id.java
index 2cae7cb..9213824 100644
--- a/syncbase/src/main/java/io/v/syncbase/Id.java
+++ b/syncbase/src/main/java/io/v/syncbase/Id.java
@@ -5,5 +5,18 @@
package io.v.syncbase;
public class Id {
- // TODO(sadovsky): Fill this in.
+ // TODO(sadovsky): Fill this in further.
+ private String mBlessing;
+ private String mName;
+
+ // TODO(sadovsky): Eliminate the code below once we've switched to io.v.syncbase.core.
+
+ protected Id(io.v.v23.services.syncbase.Id id) {
+ mBlessing = id.getBlessing();
+ mName = id.getName();
+ }
+
+ protected io.v.v23.services.syncbase.Id toVId() {
+ return new io.v.v23.services.syncbase.Id(mBlessing, mName);
+ }
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncbase.java b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
index e5e23ef..f3340f1 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncbase.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
@@ -4,19 +4,179 @@
package io.v.syncbase;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.io.File;
+
+import io.v.android.VAndroidContext;
+import io.v.android.v23.V;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+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.Constants;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.verror.ExistException;
+import io.v.v23.verror.VException;
+
public class Syncbase {
- public class DatabaseOptions {
- // TODO(sadovsky): Fill this in.
+ public static class DatabaseOptions {
+ // TODO(sadovsky): Fill this in further.
+ public String rootDir;
+ // TODO(sadovsky): Drop this once we switch from io.v.v23.syncbase to io.v.syncbase.core.
+ public VAndroidContext vAndroidContext;
}
+ private static DatabaseOptions sOpts;
+ private static Database sDatabase;
+
+ private static final String
+ TAG = "syncbase",
+ DIR_NAME = "syncbase",
+ DB_NAME = "db";
+
// Starts Syncbase if needed; creates default database if needed; reads config (e.g. cloud
// syncbase name) from options struct; performs create-or-join for "userdata" syncgroup if
- // needed; returns database handle. Async, and can fail.
- // TODO(sadovsky): The create-or-join will force this method to be async, which is annoying
- // since create-or-join will no longer be necessary once syncgroup merge is supported. Maybe
- // move create-or-join to a separate, temporarily-necessary init function?
- // TODO(sadovsky): Make this method async.
+ // needed; returns database handle.
+ // TODO(sadovsky): The create-or-join forces this method to be async, which is annoying since
+ // create-or-join will no longer be necessary once syncgroup merge is supported.
public static Database database(DatabaseOptions opts) {
- return null;
+ if (sDatabase != null) {
+ // TODO(sadovsky): Check that opts matches original opts (sOpts)?
+ return sDatabase;
+ }
+ sOpts = opts;
+ // TODO(sadovsky): Call ctx.cancel in sDatabase destructor?
+ VContext ctx = getVContext().withCancel();
+ try {
+ sDatabase = startSyncbaseAndInitDatabase(ctx);
+ } catch (Exception e) {
+ ctx.cancel();
+ throw e;
+ }
+ // TODO(sadovsky): Add create-or-join of userdata syncgroup, and make this method async.
+ return sDatabase;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // TODO(sadovsky): Remove much of the code below once we switch from io.v.v23.syncbase to
+ // io.v.syncbase.core. Note, much of this code was copied from the Todos app.
+
+ protected static VContext getVContext() {
+ return sOpts.vAndroidContext.getVContext();
+ }
+
+ // TODO(sadovsky): Some of these constants should become fields in DatabaseOptions.
+ private static final String
+ PROXY = "proxy",
+ DEFAULT_BLESSING_STRING_PREFIX = "dev.v.io:o:608941808256-43vtfndets79kf5hac8ieujto8837660.apps.googleusercontent.com:",
+ MOUNT_POINT = "/ns.dev.v.io:8101/tmp/todos/users/",
+ CLOUD_BLESSING = "dev.v.io:u:alexfandrianto@google.com";
+
+ private static Database startSyncbaseAndInitDatabase(VContext ctx) {
+ SyncbaseService s;
+ Blessings personalBlessings = getPersonalBlessings();
+ if (personalBlessings == null) {
+ throw new RuntimeException("No blessings");
+ }
+ Permissions perms = permissionsFromBlessings(personalBlessings);
+ try {
+ s = io.v.v23.syncbase.Syncbase.newService(startSyncbase(ctx, sOpts.rootDir));
+ } catch (SyncbaseServer.StartException e) {
+ throw new RuntimeException("Failed to start Syncbase", e);
+ }
+ // Create database, if needed.
+ io.v.v23.syncbase.Database d = s.getDatabase(getVContext(), DB_NAME, null);
+ try {
+ VFutures.sync(d.create(getVContext(), null));
+ } catch (ExistException e) {
+ // Database already exists, presumably from a previous run of the app.
+ } catch (VException e) {
+ throw new RuntimeException("Failed to create database", e);
+ }
+ return new Database(d);
+ }
+
+ private static String startSyncbase(VContext vContext, String rootDir)
+ throws SyncbaseServer.StartException {
+ try {
+ vContext = V.withListenSpec(vContext, V.getListenSpec(vContext).withProxy(PROXY));
+ } catch (VException e) {
+ Log.w(TAG, "Failed to set up Vanadium proxy", e);
+ }
+ File dir = new File(rootDir, DIR_NAME);
+ dir.mkdirs();
+ SyncbaseServer.Params params = new SyncbaseServer.Params()
+ .withStorageRootDir(dir.getAbsolutePath());
+ VContext serverContext = SyncbaseServer.withNewServer(vContext, params);
+ Server server = V.getServer(serverContext);
+ String name = server.getStatus().getEndpoints()[0].name();
+ Log.i(TAG, "Started Syncbase: " + name);
+ return name;
+ }
+
+ private static void checkHasOneBlessing(Blessings blessings) {
+ int n = blessings.getCertificateChains().size();
+ if (n != 1) {
+ throw new RuntimeException("Expected one blessing, got " + n);
+ }
+ }
+
+ private static String getEmailFromBlessings(Blessings blessings) {
+ checkHasOneBlessing(blessings);
+ return getEmailFromBlessingString(blessings.toString());
+ }
+
+ private static String getEmailFromBlessingPattern(BlessingPattern pattern) {
+ return getEmailFromBlessingString(pattern.toString());
+ }
+
+ private static String getEmailFromBlessingString(String blessingStr) {
+ String[] parts = blessingStr.split(":");
+ return parts[parts.length - 1];
+ }
+
+ private static String getBlessingStringFromEmail(String email) {
+ return DEFAULT_BLESSING_STRING_PREFIX + email;
+ }
+
+ private static Blessings getPersonalBlessings() {
+ return V.getPrincipal(getVContext()).blessingStore().defaultBlessings();
+ }
+
+ protected static String getPersonalBlessingString() {
+ Blessings blessings = getPersonalBlessings();
+ checkHasOneBlessing(blessings);
+ return blessings.toString();
+ }
+
+ private static String getPersonalEmail() {
+ return getEmailFromBlessings(getPersonalBlessings());
+ }
+
+ protected static Permissions permissionsFromBlessings(Blessings blessings) {
+ // TODO(sadovsky): Revisit these default perms, which were copied from the Todos app.
+ io.v.v23.security.access.AccessList openAccessList =
+ new io.v.v23.security.access.AccessList(
+ ImmutableList.of(
+ new BlessingPattern("...")),
+ ImmutableList.<String>of());
+ io.v.v23.security.access.AccessList accessList =
+ new io.v.v23.security.access.AccessList(
+ ImmutableList.of(
+ new BlessingPattern(blessings.toString()),
+ new BlessingPattern(CLOUD_BLESSING)),
+ ImmutableList.<String>of());
+ return new Permissions(ImmutableMap.of(
+ Constants.RESOLVE.getValue(), openAccessList,
+ Constants.READ.getValue(), accessList,
+ Constants.WRITE.getValue(), accessList,
+ Constants.ADMIN.getValue(), accessList));
}
}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
index 99494f3..4abc126 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
@@ -6,14 +6,14 @@
public class Syncgroup {
public Id getId() {
- return null;
+ throw new RuntimeException("Not implemented");
}
public AccessList getAccessList() {
- return null;
+ throw new RuntimeException("Not implemented");
}
- public class UpdateAccessListOptions {
+ public static class UpdateAccessListOptions {
// TODO(sadovsky): Fill this in.
}
@@ -21,15 +21,15 @@
// Setting opts.syncgroupOnly makes it so these methods only update the AccessList for the
// syncgroup.
public void addUsers(User[] users, AccessLevel level, UpdateAccessListOptions opts) {
-
+ throw new RuntimeException("Not implemented");
}
public void removeUsers(User[] users, UpdateAccessListOptions opts) {
-
+ throw new RuntimeException("Not implemented");
}
// Applies 'delta' to the AccessList. Note, NULL enum means "remove".
public void updateAccessList(AccessList delta, UpdateAccessListOptions opts) {
-
+ throw new RuntimeException("Not implemented");
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/WatchChange.java b/syncbase/src/main/java/io/v/syncbase/WatchChange.java
index e728f3a..0269413 100644
--- a/syncbase/src/main/java/io/v/syncbase/WatchChange.java
+++ b/syncbase/src/main/java/io/v/syncbase/WatchChange.java
@@ -4,6 +4,18 @@
package io.v.syncbase;
+import io.v.v23.services.watch.Change;
+
public class WatchChange {
- // TODO(sadovsky): Fill this in.
+ // TODO(sadovsky): Fill this in further.
+
+ // TODO(sadovsky): Eliminate the code below once we've switched to io.v.syncbase.core.
+
+ protected WatchChange(Change c) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ protected Change toVChange() {
+ throw new RuntimeException("Not implemented");
+ }
}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/WatchChangeHandler.java b/syncbase/src/main/java/io/v/syncbase/WatchChangeHandler.java
index b7ef9d9..c8d27b6 100644
--- a/syncbase/src/main/java/io/v/syncbase/WatchChangeHandler.java
+++ b/syncbase/src/main/java/io/v/syncbase/WatchChangeHandler.java
@@ -14,11 +14,14 @@
// void onChangeBatch(Iterator<WatchChange> values, Iterator<WatchChange> changes)
void onInitialState(Iterator<WatchChange> values) {
+
}
void onChangeBatch(Iterator<WatchChange> changes) {
+
}
void onError(Exception e) {
+
}
}
diff --git a/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java b/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java
new file mode 100644
index 0000000..473e742
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java
@@ -0,0 +1,16 @@
+// 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.syncbase;
+
+import org.junit.Test;
+
+public class SyncbaseTest {
+ @Test
+ public void createDatabase() {
+ Syncbase.DatabaseOptions opts = new Syncbase.DatabaseOptions();
+ opts.mRootDir = "/tmp";
+ Syncbase.database(opts);
+ }
+}
\ No newline at end of file