Merge changes from topic 'baku'
* changes:
Baku - incrementally adding docs
Baku - Usability improvements
Baku - Derived SG suffixes
Baku - Fixing cloud sync permissions.
Baku Toolkit - Fixing blessings stuff
Baku Toolkit - upgrading to BlessingsManager
diff --git a/baku-toolkit/bootstrap-cloudsync b/baku-toolkit/bootstrap-cloudsync
new file mode 100755
index 0000000..cf944e9
--- /dev/null
+++ b/baku-toolkit/bootstrap-cloudsync
@@ -0,0 +1,22 @@
+#!/bin/bash
+# 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.
+
+if [ ${host-} ]; then
+ client=${client-608941808256-43vtfndets79kf5hac8ieujto8837660.apps.googleusercontent.com}
+
+ blessings="dev.v.io:o:$client"
+
+ name="tmp/clients/$client/cloudsync"
+ echo "Running syncbased on $host as $name"
+
+ $JIRI_ROOT/release/go/bin/dmrun --debug --ssh $host \
+ --sshoptions "-i $HOME/.ssh/google_compute_engine" \
+ $JIRI_ROOT/release/go/bin/syncbased --root-dir="/tmp/syncbase" \
+ --name=$name --v23.tcp.address=:8199 \
+ --v23.namespace.root=/ns.dev.v.io:8101 \
+ --v23.permissions.literal="{\"Admin\":{\"In\":[\"${blessings}\"]},\"Write\":{\"In\":[\"${blessings}\"]},\"Read\":{\"In\":[\"${blessings}\"]},\"Resolve\":{\"In\":[\"${blessings}\"]},\"Debug\":{\"In\":[\"${blessings}\"]}}"
+else
+ echo "Missing required environment variable host."
+fi
diff --git a/baku-toolkit/build.gradle b/baku-toolkit/build.gradle
index be586c1..62608d7 100644
--- a/baku-toolkit/build.gradle
+++ b/baku-toolkit/build.gradle
@@ -6,11 +6,13 @@
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.1'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
- classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
- classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
- classpath 'me.tatarka:gradle-retrolambda:3.2.4'
+ classpath (
+ 'com.android.tools.build:gradle:1.5.0',
+ 'com.github.dcendents:android-maven-gradle-plugin:1.3',
+ 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0',
+ 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6',
+ 'me.tatarka:gradle-retrolambda:3.2.4'
+ )
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/baku-toolkit/clean-cloudsync b/baku-toolkit/clean-cloudsync
new file mode 100755
index 0000000..deadc90
--- /dev/null
+++ b/baku-toolkit/clean-cloudsync
@@ -0,0 +1,21 @@
+#!/bin/bash
+# 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.
+
+
+if [ ${host-} ]; then
+ ssh="/usr/bin/ssh -i $HOME/.ssh/google_compute_engine $host"
+
+ fullinstallation=`$ssh ls /tmp/dmrun/dm/dmroot/app'*'`
+ installation=${fullinstallation:13}
+
+ fullinstance=`$ssh ls /tmp/dmrun/dm/dmroot/app*/*/instances`
+ instance=${fullinstance:9}
+
+ ${JIRI_ROOT}/release/go/bin/device kill /$host:8150/apps/app/$installation/$instance
+ eval "$ssh sudo killall -9 agentd deviced"
+ eval "$ssh rm -rf /tmp/'*'"
+else
+ echo "Missing required environment variable host."
+fi
diff --git a/baku-toolkit/gradle/wrapper/gradle-wrapper.properties b/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
index 89af0bd..b39d8fb 100644
--- a/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
+++ b/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip
diff --git a/baku-toolkit/lib/build.gradle b/baku-toolkit/lib/build.gradle
index e6563bd..1a6d960 100644
--- a/baku-toolkit/lib/build.gradle
+++ b/baku-toolkit/lib/build.gradle
@@ -1,6 +1,11 @@
+import org.gradle.api.tasks.javadoc.internal.JavadocSpec
+import org.gradle.jvm.internal.toolchain.JavaToolChainInternal
+import org.gradle.jvm.platform.JavaPlatform
+import org.gradle.jvm.platform.internal.DefaultJavaPlatform
+import org.gradle.language.base.internal.compile.Compiler
// You should change this after releasing a new version of the Baku Toolkit. See the
// list of published versions at https://repo1.maven.org/maven2/io/v/baku-toolkit.
-version = '0.6.0'
+version = '0.7.0'
group = 'io.v'
def siteUrl = 'https://github.com/vanadium/java'
@@ -39,10 +44,6 @@
dependencies {
provided(
- /* If the application wishes to use support libraries, it should include them as compile
- dependencies in its own build.gradle. */
- 'com.android.support:appcompat-v7:23.0.1',
- 'com.android.support:recyclerview-v7:23.0.1',
/*
https://projectlombok.org/setup/android.html
Follow Android Studio instructions at the bottom of the page to install the Lombok
@@ -52,14 +53,13 @@
'org.slf4j:slf4j-api:1.7.12'
)
- androidTestCompile('org.slf4j:slf4j-android:1.7.12')
+ androidTestCompile 'org.slf4j:slf4j-android:1.7.12'
testCompile(
'org.mockito:mockito-core:1.10.19',
'org.powermock:powermock-classloading-xstream:1.6.3',
'org.powermock:powermock-module-junit4:1.6.3',
'org.powermock:powermock-module-junit4-rule:1.6.3',
- 'org.robolectric:robolectric:3.0',
'org.slf4j:slf4j-simple:1.7.12'
)
@@ -68,12 +68,18 @@
}
compile(
+ /*
+ Ideally this would be optional, but this unexpectedly becomes required if you touch
+ the Baku CollectionBinding Builder, even if you don't actually try to bind to a
+ RecyclerView.
+ */
+ 'com.android.support:recyclerview-v7:23.0.1',
'com.jakewharton.rxbinding:rxbinding:0.3.0',
'commons-io:commons-io:2.4',
- 'io.reactivex:rxandroid:1.0.1',
- 'io.reactivex:rxjava:1.0.16',
+ 'io.reactivex:rxandroid:1.1.0',
+ 'io.reactivex:rxjava:1.0.17',
'io.reactivex:rxjava-async-util:0.21.0',
- 'io.v:vanadium-android:1.0',
+ 'io.v:vanadium-android:1.7',
'net.javacrumbs.future-converter:future-converter-guava-rxjava:0.3.0',
'net.sourceforge.streamsupport:streamsupport:1.3.2',
'org.robotninjas:fluent-futures:1.0'
@@ -128,16 +134,98 @@
classifier = 'sources'
}
-task javadoc (type: Javadoc) {
+task javadoc (type: FullProcessingJavadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+ options.overview = 'src/main/java/overview.html'
+
if (JavaVersion.current().isJava8Compatible()) {
// TODO(rosswang): Can we get rid of this?
options.addStringOption('Xdoclint:none', '-quiet')
}
}
+/**
+ * https://groups.google.com/d/topic/gradle-dev/R83dy_6PHMc/discussion
+ */
+@ParallelizableTask
+class FullProcessingJavadoc extends Javadoc {
+ private Set<File> sourceDirs
+
+ @Override
+ public void setSource(Object sourceDirs) {
+ this.sourceDirs = sourceDirs
+ super.setSource(sourceDirs)
+ }
+
+ @Override
+ protected void generate() {
+ final File destinationDir = getDestinationDir();
+
+ if (options.getDestinationDirectory() == null) {
+ options.destinationDirectory(destinationDir);
+ }
+
+ options.classpath(new ArrayList<File>(getClasspath().getFiles()));
+
+ if (!GUtil.isTrue(options.getWindowTitle()) && GUtil.isTrue(getTitle())) {
+ options.windowTitle(getTitle());
+ }
+ if (options instanceof StandardJavadocDocletOptions) {
+ StandardJavadocDocletOptions docletOptions = (StandardJavadocDocletOptions) options;
+ if (!GUtil.isTrue(docletOptions.getDocTitle()) && GUtil.isTrue(getTitle())) {
+ docletOptions.setDocTitle(getTitle());
+ }
+ }
+
+ if (maxMemory != null) {
+ final List<String> jFlags = options.getJFlags();
+ final Iterator<String> jFlagsIt = jFlags.iterator();
+ boolean containsXmx = false;
+ while (!containsXmx && jFlagsIt.hasNext()) {
+ final String jFlag = jFlagsIt.next();
+ if (jFlag.startsWith("-Xmx")) {
+ containsXmx = true;
+ }
+ }
+ if (!containsXmx) {
+ options.jFlags("-Xmx" + maxMemory);
+ }
+ }
+
+ Set<String> roots = new HashSet<>();
+ for (File sourceDir : sourceDirs) {
+ roots += sourceDir.absolutePath
+ }
+ /*Set<String> packages = new HashSet<>();
+ for (File sourceFile : getSource()) {
+ println sourceFile.getPath()
+ }*/
+ options.setSourceNames(Arrays.asList('io.v'));
+ options.addStringOption('subpackages', 'io.v')
+ options.addStringsOption('sourcepath').value = new ArrayList<>(roots)
+
+ executeExternalJavadoc();
+ }
+
+ private void executeExternalJavadoc() {
+ JavadocSpec spec = new JavadocSpec();
+ spec.setExecutable(executable);
+ spec.setOptions(options);
+ spec.setIgnoreFailures(!failOnError);
+ spec.setWorkingDir(getProject().getProjectDir());
+ spec.setOptionsFile(getOptionsFile());
+
+ Compiler<JavadocSpec> generator = ((JavaToolChainInternal) getToolChain()).select(getPlatform()).newCompiler(JavadocSpec.class);
+ generator.execute(spec);
+ }
+
+ private static JavaPlatform getPlatform() {
+ return DefaultJavaPlatform.current();
+ }
+}
+
task javadocJar(type: Jar, dependsOn: javadoc) {
from tasks.javadoc
classifier = 'javadoc'
@@ -156,15 +244,7 @@
user = project.properties.bintrayUsername
key = project.properties.bintrayApiKey
- // TODO(rosswang): use - https://github.com/bintray/gradle-bintray-plugin/issues/81
- //configurations = ['archives']
- // TODO(rosswang): remove - https://github.com/bintray/gradle-bintray-plugin/issues/81
- filesSpec {
- from 'build/libs', 'build/outputs/aar', 'build/poms'
- rename '.+\\.aar', artifactPrefix + '.aar'
- rename 'pom-default\\.xml', artifactPrefix + '.pom'
- into 'io/v/baku-toolkit/' + version
- }
+ configurations = ['archives']
pkg {
desc = pkgDesc
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/VAndroidTestCase.java b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/VAndroidTestCase.java
index 5974054..824af5a 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/VAndroidTestCase.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/VAndroidTestCase.java
@@ -11,7 +11,7 @@
import java.util.concurrent.TimeUnit;
import io.v.android.v23.V;
-import io.v.debug.SyncbaseClient;
+import io.v.debug.SyncbaseAndroidClient;
import io.v.v23.context.VContext;
import lombok.Getter;
import lombok.experimental.Accessors;
@@ -72,15 +72,12 @@
@Override
protected void tearDown() throws Exception {
- // TODO(rosswang): https://github.com/vanadium/issues/issues/809
- // We can't shut down Vanadium because we can't shut down Syncbase. Nothing will fail
- // outright, but operations on subsequent uses of Syncbase will hang indefinitely.
- //V.shutdown();
+ mVContext.cancel();
super.tearDown();
}
- public SyncbaseClient createSyncbaseClient() {
+ public SyncbaseAndroidClient createSyncbaseClient() {
// TODO(rosswang): zero duration after https://github.com/vanadium/issues/issues/809
- return new SyncbaseClient(getContext(), null, true, Duration.standardMinutes(2));
+ return new SyncbaseAndroidClient(getContext(), null, true, Duration.standardMinutes(2));
}
}
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
index 60ababf..2aca0b0 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
@@ -9,17 +9,17 @@
import com.google.common.base.Throwables;
import io.v.baku.toolkit.VAndroidTestCase;
-import io.v.rx.syncbase.RxSyncbase;
+import io.v.rx.syncbase.RxAndroidSyncbase;
import io.v.rx.syncbase.RxTable;
public class CollectionBindingTest extends VAndroidTestCase {
- private RxSyncbase mRxSyncbase;
+ private RxAndroidSyncbase mRxSyncbase;
private RxTable mTable;
@Override
protected void setUp() throws Exception {
super.setUp();
- mRxSyncbase = new RxSyncbase(getVContext(), createSyncbaseClient());
+ mRxSyncbase = new RxAndroidSyncbase(getVContext(), createSyncbaseClient());
mTable = mRxSyncbase.rxApp(getClass().getName()).rxDb("db").rxTable("t");
}
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/debug/SyncbaseClientTest.java b/baku-toolkit/lib/src/androidTest/java/io/v/debug/SyncbaseClientTest.java
index bb319b5..c1f9d47 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/debug/SyncbaseClientTest.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/debug/SyncbaseClientTest.java
@@ -17,7 +17,7 @@
public void testAppPersistence() throws Exception {
final VContext ctx = getVContext();
- try (final SyncbaseClient sb = createSyncbaseClient()) {
+ try (final SyncbaseAndroidClient sb = createSyncbaseClient()) {
final SyncbaseApp app = first(sb.getRxClient()).getApp(APP);
try {
sync(app.create(ctx, null));
@@ -26,7 +26,7 @@
assertEquals(true, (boolean) sync(app.exists(ctx)));
}
- try (final SyncbaseClient sb = createSyncbaseClient()) {
+ try (final SyncbaseAndroidClient sb = createSyncbaseClient()) {
final SyncbaseApp app = first(sb.getRxClient()).getApp(APP);
assertEquals(true, (boolean) sync(app.exists(ctx)));
sync(app.destroy(ctx));
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/RxSyncbaseTest.java b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/RxSyncbaseTest.java
index 1e9ba56..62b6a97 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/RxSyncbaseTest.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/RxSyncbaseTest.java
@@ -21,13 +21,13 @@
@Accessors(prefix = "m")
@Getter
public class RxSyncbaseTest extends VAndroidTestCase {
- private RxSyncbase mRxSyncbase;
+ private RxAndroidSyncbase mRxSyncbase;
private RxTable mTable;
@Override
protected void setUp() throws Exception {
super.setUp();
- mRxSyncbase = new RxSyncbase(getVContext(), createSyncbaseClient());
+ mRxSyncbase = new RxAndroidSyncbase(getVContext(), createSyncbaseClient());
mTable = mRxSyncbase.rxApp(getClass().getName()).rxDb("db").rxTable("t");
}
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilLocalTest.java b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilLocalTest.java
index c078b27..c2ea275 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilLocalTest.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilLocalTest.java
@@ -51,7 +51,6 @@
@Override
protected void tearDown() throws Exception {
- mMountTable.stop();
FileUtils.deleteDirectory(mStorageRoot);
super.tearDown();
}
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilTestCases.java b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilTestCases.java
index 5467439..c969d05 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilTestCases.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/rx/syncbase/SgHostUtilTestCases.java
@@ -10,7 +10,7 @@
import java.util.concurrent.TimeUnit;
-import io.v.debug.SyncbaseClient;
+import io.v.debug.SyncbaseAndroidClient;
import io.v.impl.google.naming.NamingUtil;
import io.v.v23.context.VContext;
import lombok.RequiredArgsConstructor;
@@ -48,7 +48,7 @@
public void testEnsureSgHost() {
final String name = name("users/jenkins.veyron@gmail.com/integ/ensuredsghost");
- try (final SyncbaseClient sb = new SyncbaseClient(mContext, null)) {
+ try (final SyncbaseAndroidClient sb = new SyncbaseAndroidClient(mContext, null)) {
block(SgHostUtil.ensureSyncgroupHost(mVContext, sb.getRxServer(), name)).first();
assertTrue(block(SgHostUtil.isSyncbaseOnline(mVContext, name)).first());
}
@@ -58,13 +58,13 @@
/*public void testGlobalUserSyncgroup() {
final Observable<Blessings> blessings =
Observable.just(V.getPrincipal(mVContext).blessingStore().forPeer("..."));
- try (final SyncbaseClient sb = new SyncbaseClient(mContext, blessings)) {
+ try (final SyncbaseAndroidClient sb = new SyncbaseAndroidClient(mContext, blessings)) {
final RxSyncbase rsb = new RxSyncbase(mVContext, sb);
- block(GlobalUserSyncgroup.builder()
+ block(UserPeerSyncgroup.builder()
.syncbase(rsb)
.db(rsb.rxApp("app").rxDb("db"))
.sgSuffix("test")
- .syncHostLevel(new UserAppSyncHost("integ"))
+ .syncHostLevel(new ClientLevelCloudSync("integ"))
.rxBlessings(blessings)
.build()
.rxJoin()).first();
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
index 3e424ea..0277502 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
@@ -14,6 +14,8 @@
/**
* A default integration with {@link BakuActivityTrait} extending {@link android.app.Activity}. Most
* activities with distributed state should inherit from this.
+ *
+ * @see io.v.baku.toolkit
*/
@Slf4j
public abstract class BakuActivity extends VActivity implements BakuActivityTrait<Activity> {
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
index 79975d6..f222070 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
@@ -7,19 +7,19 @@
import android.app.Activity;
import android.os.Bundle;
-import io.v.baku.toolkit.bind.SyncbaseBinding;
import io.v.baku.toolkit.bind.CollectionBinding;
+import io.v.baku.toolkit.bind.SyncbaseBinding;
import io.v.baku.toolkit.syncbase.BakuDb;
import io.v.baku.toolkit.syncbase.BakuSyncbase;
import io.v.baku.toolkit.syncbase.BakuTable;
-import io.v.rx.syncbase.GlobalUserSyncgroup;
+import io.v.rx.syncbase.UserCloudSyncgroup;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import rx.subscriptions.CompositeSubscription;
/**
- * Activity trait for activities with distributed UI state. By default, shared state is stored
+ * Activity mix-in for activities with distributed UI state. By default, shared state is stored
* in Syncbase under <i>app.package.name</i>/db/ui.
* <p>
* Default activity extensions incorporating this mix-in are available:
@@ -29,7 +29,11 @@
* </ul>
* <p>
* Since Java doesn't actually support multiple inheritance, clients requiring custom inheritance
- * hierarchies will need to wire in manually, like any of the examples above.
+ * hierarchies will need to wire in manually, like any of the examples above. Alternatively, this
+ * class may be used via pure composition, as detailed at
+ * {@link BakuActivityMixin#BakuActivityMixin(Activity, Bundle)}.
+ *
+ * @see io.v.baku.toolkit
*/
@Accessors(prefix = "m")
@Slf4j
@@ -70,7 +74,7 @@
* private BakuActivityTrait<SampleCompositionActivity> mBaku;
*
* @Override
- * protected void onCreate(Bundle savedInstanceState) {
+ * protected void onCreate(final Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
* setContentView(R.layout.activity_hello);
*
@@ -108,7 +112,7 @@
}
protected void joinInitialSyncGroup() {
- GlobalUserSyncgroup.forActivity(this).join();
+ UserCloudSyncgroup.forActivity(this).join();
}
public void onSyncError(final Throwable t) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
index 9fbd4ec..9967814 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
@@ -13,6 +13,9 @@
import io.v.baku.toolkit.syncbase.BakuTable;
import rx.subscriptions.CompositeSubscription;
+/**
+ * @see BakuActivityMixin
+ */
public interface BakuActivityTrait<T extends Activity> extends AutoCloseable {
VAndroidContextTrait<T> getVAndroidContextTrait();
BakuSyncbase getSyncbase();
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
index 14220dd..85c0da8 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
@@ -14,6 +14,8 @@
/**
* A default integration with {@link BakuActivityTrait} extending
* {@link android.support.v7.app.AppCompatActivity}.
+ *
+ * @see io.v.baku.toolkit
*/
@Slf4j
public abstract class BakuAppCompatActivity
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
index 8ebd442..1164a0f 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
@@ -32,4 +32,10 @@
super.onCreate(savedInstanceState, persistentState);
mVAndroidContextTrait = createVActivityTrait(savedInstanceState);
}
+
+ @Override
+ protected void onDestroy() {
+ mVAndroidContextTrait.close();
+ super.onDestroy();
+ }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextMixin.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextMixin.java
index 467cc7e..03a3ad4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextMixin.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextMixin.java
@@ -12,7 +12,7 @@
import android.os.Bundle;
import io.v.android.v23.V;
-import io.v.baku.toolkit.blessings.AccountManagerBlessingsFragment;
+import io.v.baku.toolkit.blessings.BlessingsManagerBlessingsProvider;
import io.v.baku.toolkit.blessings.BlessingsProvider;
import io.v.baku.toolkit.blessings.BlessingsUtils;
import io.v.baku.toolkit.debug.DebugFragment;
@@ -21,20 +21,21 @@
import io.v.v23.context.VContext;
import io.v.v23.security.Blessings;
import io.v.v23.verror.VException;
+import java8.util.function.BiFunction;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
/**
- * Android context mix-in incorporating common Vanadium utilities:
+ * Android context mix-in incorporating common Vanadium utilities. These include:
* <ul>
* <li>Vanadium initialization during {@code onCreate}; context available via
* {@code getVContext}</li>
- * <li>Blessings management, available via {@code getBlessingsProvider().getRxBlessings}.
- * Upon {@code subscribe}, blessings are refreshed from the {@code BlessingsManager} or sought from
- * the {@code BlessingsProvider} (by default, the Account Manager).</li>
+ * <li>Blessings management, available via {@link BlessingsProvider#getRxBlessings()
+ * getBlessingsProvider().getRxBlessings()}. Upon {@code subscribe}, blessings are refreshed from
+ * the {@code BlessingsManager} or sought from the {@code BlessingsProvider} (by default, the
+ * Vanadium {@link io.v.android.libs.security.BlessingsManager}).</li>
* </ul>
- * <p>
* Default activity extensions incorporating this mix-in are available:
* <ul>
* <li>{@link VActivity} (extends {@link Activity})</li>
@@ -70,17 +71,6 @@
try {
return V.init(mAndroidContext, getSavedOptions());
} catch (final RuntimeException e) {
- try {
- /* V.shutdown so we might try V.init again if warranted. If we don't V.shutdown
- first, the process can abruptly die. It seems that if this happens, Android might
- just restart the app immediately, i.e. before we've been able to display an
- intelligible error message. */
- V.shutdown();
- } catch (final RuntimeException e2) {
- log.error("Unable to clean up failed Vanadium initialization", e2);
- e.addSuppressed(e2);
- }
-
if (mVanadiumPreferences.getAll().isEmpty()) {
throw e;
} else {
@@ -93,18 +83,30 @@
public VAndroidContextMixin(final T androidContext, final BlessingsProvider blessingsProvider,
final ErrorReporter errorReporter) {
+ this(androidContext, (x, y) -> blessingsProvider, errorReporter);
+ }
+
+ public VAndroidContextMixin(
+ final T androidContext, final BiFunction<? super VContext, ? super T, BlessingsProvider>
+ blessingsProviderFactory, final ErrorReporter errorReporter) {
mAndroidContext = androidContext;
- mBlessingsProvider = blessingsProvider;
mErrorReporter = errorReporter;
mVanadiumPreferences = getVanadiumPreferences(mAndroidContext);
mVContext = vinit();
+ mBlessingsProvider = blessingsProviderFactory.apply(mVContext, mAndroidContext);
+
//Any time our blessings change, we need to attach them to our principal.
mBlessingsProvider.getPassiveRxBlessings().subscribe(this::processBlessings,
t -> errorReporter.onError(R.string.err_blessings_misc, t));
}
+ @Override
+ public void close() {
+ mVContext.cancel();
+ }
+
protected void processBlessings(final Blessings blessings) {
try {
BlessingsUtils.assumeBlessings(mVContext, blessings);
@@ -116,15 +118,12 @@
public static <T extends Activity> VAndroidContextMixin<T> withDefaults(
final T activity, final Bundle savedInstanceState) {
final FragmentManager mgr = activity.getFragmentManager();
- final AccountManagerBlessingsFragment blessingsProvider;
final ErrorReporterFragment errorReporter;
if (savedInstanceState == null) {
- blessingsProvider = new AccountManagerBlessingsFragment();
errorReporter = new ErrorReporterFragment();
final FragmentTransaction t = mgr.beginTransaction()
- .add(blessingsProvider, AccountManagerBlessingsFragment.TAG)
.add(errorReporter, ErrorReporterFragment.TAG);
if (DebugUtils.isApkDebug(activity)) {
@@ -133,9 +132,9 @@
}
t.commit();
} else {
- blessingsProvider = AccountManagerBlessingsFragment.find(mgr);
errorReporter = ErrorReporterFragment.find(mgr);
}
- return new VAndroidContextMixin<>(activity, blessingsProvider, errorReporter);
+ return new VAndroidContextMixin<>(activity, BlessingsManagerBlessingsProvider::new,
+ errorReporter);
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextTrait.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextTrait.java
index 4744a8b..63eaaf4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextTrait.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAndroidContextTrait.java
@@ -9,11 +9,12 @@
import io.v.baku.toolkit.blessings.BlessingsProvider;
import io.v.v23.context.VContext;
-public interface VAndroidContextTrait<T extends Context> {
+public interface VAndroidContextTrait<T extends Context> extends AutoCloseable {
String VANADIUM_OPTIONS_SHARED_PREFS = "VanadiumOptions";
T getAndroidContext();
BlessingsProvider getBlessingsProvider();
ErrorReporter getErrorReporter();
VContext getVContext();
+ void close();
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
index c3ca36c..f5b453e 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
@@ -37,4 +37,10 @@
super.onCreate(savedInstanceState, persistentState);
mVAndroidContextTrait = createVActivityTrait(savedInstanceState);
}
+
+ @Override
+ protected void onDestroy() {
+ mVAndroidContextTrait.close();
+ super.onDestroy();
+ }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java
index d11f93a..0c4d7b2 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java
@@ -4,6 +4,7 @@
package io.v.baku.toolkit.bind;
+import android.support.annotation.IdRes;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ListView;
@@ -88,4 +89,12 @@
throw new IllegalArgumentException("No default binding for view " + view);
}
}
+
+ /**
+ * Binds to the view identified by {@code viewId}.
+ * @see #bindTo(View)
+ */
+ public RangeAdapter bindTo(final @IdRes int viewId) {
+ return bindTo(mBase.mActivity.findViewById(viewId));
+ }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java
index b1085f3..600c3f5 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java
@@ -6,6 +6,8 @@
import com.google.common.collect.Ordering;
+import java.util.Comparator;
+
import io.v.rx.syncbase.RangeWatchBatch;
import io.v.rx.syncbase.RxTable;
import io.v.v23.syncbase.nosql.PrefixRange;
@@ -21,7 +23,7 @@
extends CollectionAdapterBuilder<PrefixBindingBuilder<T, A>, RxTable.Row<T>, A> {
private Class<T> mType;
private PrefixRange mPrefix;
- private Ordering<? super RxTable.Row<T>> mOrdering;
+ private Comparator<? super RxTable.Row<T>> mOrdering;
private Func1<String, Boolean> mKeyFilter;
public PrefixBindingBuilder(final CollectionBinding.Builder base) {
@@ -38,9 +40,11 @@
}
/**
+ * The element type for the collection, i.e. the value type for rows matching the prefix and key
+ * filter.
+ * <p>
* This setter is minimally typesafe; after setting the {@code type}, clients should
- * probably also update {@code ordering} and {@code viewAdapter}. If intending to use a
- * collection binding that requires a
+ * probably also update {@code ordering} and {@code viewAdapter}.
*/
public <U> PrefixBindingBuilder<U, A> type(final Class<U> type) {
@SuppressWarnings("unchecked")
@@ -68,12 +72,19 @@
return new TextViewAdapter(mBase.getDefaultViewAdapterContext()).map(RxTable.Row::getValue);
}
+ private Class<T> getType() {
+ if (mType == null) {
+ throw new IllegalStateException("Missing required type property");
+ }
+ return mType;
+ }
+
/**
* For comparable {@code T}, default to natural ordering on values. Otherwise, default to
* natural ordering on row names.
*/
private Ordering<? super RxTable.Row<? extends T>> getDefaultOrdering() {
- if (mOrdering == null && Comparable.class.isAssignableFrom(mType)) {
+ if (mOrdering == null && Comparable.class.isAssignableFrom(getType())) {
return Ordering.natural().onResultOf(r -> (Comparable) r.getValue());
} else {
return Ordering.natural().onResultOf(RxTable.Row::getRowName);
@@ -86,14 +97,11 @@
}
public Observable<RangeWatchBatch<T>> buildPrefixWatch() {
- if (mType == null) {
- throw new IllegalStateException("Missing required type property");
- }
return mBase.mRxTable.watch(mPrefix == null? RowRange.prefix("") : mPrefix,
- mKeyFilter, mType);
+ mKeyFilter, getType());
}
- private Ordering<? super RxTable.Row<T>> getOrdering() {
+ private Comparator<? super RxTable.Row<T>> getOrdering() {
return mOrdering == null ? getDefaultOrdering() : mOrdering;
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java
index 9bd3c57..117fc87 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java
@@ -10,6 +10,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
@@ -35,13 +36,17 @@
private final Map<String, T> mRows = new HashMap<>();
private final List<RxTable.Row<T>> mSorted = new ArrayList<>();
- private final Ordering<? super RxTable.Row<T>> mOrdering;
+ private final Comparator<? super RxTable.Row<T>> mOrdering;
- public PrefixListAccumulator(final Ordering<? super RxTable.Row<T>> ordering) {
+ public PrefixListAccumulator(final Comparator<? super RxTable.Row<T>> ordering) {
// ensure deterministic ordering by always applying secondary order on row name
- mOrdering = ordering.compound(Ordering.natural().onResultOf(RxTable.Row::getRowName));
+ mOrdering = Ordering.from(ordering).compound(
+ Ordering.natural().onResultOf(RxTable.Row::getRowName));
}
+ /**
+ * The generic wildcard is for the benefit of subclass overrides.
+ */
public Observable<? extends PrefixListAccumulator<T>> scanFrom(
final Observable<RangeWatchBatch<T>> watch) {
return watch
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListDeltaAccumulator.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListDeltaAccumulator.java
index d804799..7d5a232 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListDeltaAccumulator.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListDeltaAccumulator.java
@@ -6,7 +6,7 @@
import android.support.v7.widget.RecyclerView;
-import com.google.common.collect.Ordering;
+import java.util.Comparator;
import io.v.rx.syncbase.RangeWatchBatch;
import io.v.rx.syncbase.RxTable;
@@ -24,7 +24,7 @@
private Consumer<RecyclerView.Adapter> mDeltas;
private final NumericIdMapper mIds = new NumericIdMapper();
- public PrefixListDeltaAccumulator(final Ordering<? super RxTable.Row<T>> ordering) {
+ public PrefixListDeltaAccumulator(final Comparator<? super RxTable.Row<T>> ordering) {
super(ordering);
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/bindings.png b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/bindings.png
new file mode 100644
index 0000000..bbfad53
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/bindings.png
Binary files differ
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/mvvm.png b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/mvvm.png
new file mode 100644
index 0000000..25b2909
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/doc-files/mvvm.png
Binary files differ
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/package-info.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/package-info.java
index 4bdca83..10ff1a4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/package-info.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/package-info.java
@@ -3,10 +3,44 @@
// license that can be found in the LICENSE file.
/**
- * Constructs:
- * <ul>
- * <li>Binding - maps one or more controls/properties to a Syncbase row.</li>
- * <li>Coordination policy - integrates uplinks and downlinks.</li>
- * </ul>
+ * These classes provide bindings between Android widgets and Syncbase data. For the reasons
+ * outlined in {@link io.v.rx.syncbase}, Vanadium state distribution with Syncbase would ideally be
+ * done with pure FRP <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel"
+ * target="_blank">MVVM</a>, with distributed state elements modeled in Syncbase.
+ * <p>
+ * <img src="doc-files/mvvm.png">
+ * <p>
+ * <b>Fig: Ideal MVVM for distributed apps</b>
+ * <p>
+ * However, while this is easily accomplished with
+ * <a href="https://flutter.io/" target="_blank">Flutter</a>, the Baku Toolkit would need to
+ * implement an Android viewmodel layer to achieve the same for Android Java, which starts to amount
+ * to reimplementing Flutter for Java. Instead, the Baku Android Toolkit offers data bindings that
+ * allow Syncbase to drive more conventional Android Java UI widgets, and to allow those widgets to
+ * update distributed state in Syncbase.
+ * <p>
+ * <img src="doc-files/bindings.png">
+ * <p>
+ * <b>Fig: Baku data bindings without viewmodel</b>
+ * <p>
+ * Even though these data bindings are not true MVVM, app developers are encouraged to treat them
+ * declaratively, and to make use of pure functional transformations wherever possible to simplify
+ * their code. Imperative code is however still useful for Android initialization, implementing
+ * reactive widget update logic, and writing to Syncbase.
+ * <p>
+ * Data bindings are offered to client applications via builders. Many facets of bindings are
+ * derived from their usage context. For example, {@code bindTo(...)} methods perform type
+ * introspection to construct appropriate binding types for the widget being bound (and possibly the
+ * row type being bound to). In the future, we may add a plug-in to preprocess Android layout markup
+ * similar to the <a href="http://developer.android.com/tools/data-binding/guide.html"
+ * target="_blank">Android Data Binding Library</a>.
+ * <p>
+ * At present, for simplicity, each data binding that reads from Syncbase has its own Syncbase watch
+ * stream. If this ends up wasting resources and degrading performance, we can optimize to minimize
+ * the number of watch streams and broadcast filtered streams to each data binding.
+ * <p>
+ * Offering data bindings rather than pure functional MVVM transforms does introduce some
+ * coordination concerns between read and write bindings. Strategies for dealing with coordination
+ * are included in the toolkit.
*/
package io.v.baku.toolkit.bind;
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AbstractRefreshableBlessingsProvider.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AbstractRefreshableBlessingsProvider.java
new file mode 100644
index 0000000..e6ef88c
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AbstractRefreshableBlessingsProvider.java
@@ -0,0 +1,95 @@
+// 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.baku.toolkit.blessings;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import net.javacrumbs.futureconverter.guavarx.FutureConverter;
+
+import io.v.v23.security.Blessings;
+import lombok.Getter;
+import lombok.Synchronized;
+import lombok.experimental.Accessors;
+import rx.Observable;
+import rx.subjects.PublishSubject;
+
+@Accessors(prefix = "m")
+public abstract class AbstractRefreshableBlessingsProvider implements RefreshableBlessingsProvider {
+ /**
+ * An observable that, when subscribed to, refreshes the blessing. If the
+ * {@link io.v.android.libs.security.BlessingsManager} needs to be invoked for the refresh, the
+ * subscription will not produce results until the invocation completes. Subsequently, it will
+ * receive all blessings refreshed via {@link #refreshBlessings()} and other subscriptions to
+ * {@link #getRxBlessings()}.
+ */
+ @Getter
+ private final Observable<Blessings> mRxBlessings;
+ /**
+ * An observable for the blessings that does not refresh when subscribed to. Upon subscription,
+ * this will produce the last known blessing. It will subsequently receive all blessings
+ * refreshed via {@link #refreshBlessings()} and subscriptions to {@link #getRxBlessings()}.
+ */
+ @Getter
+ private final Observable<Blessings> mPassiveRxBlessings;
+
+ private final PublishSubject<ListenableFuture<Blessings>> mPub;
+ private Blessings mLastBlessings;
+ private ListenableFuture<Blessings> mCurrentSeek;
+ private final Object mSeekLock = new Object();
+
+ public AbstractRefreshableBlessingsProvider() {
+ this(null);
+ }
+
+ public AbstractRefreshableBlessingsProvider(final ListenableFuture<Blessings> seekInProgress) {
+ mCurrentSeek = seekInProgress;
+
+ mPub = PublishSubject.create();
+ mPassiveRxBlessings = mPub
+ .flatMap(FutureConverter::toObservable)
+ .distinctUntilChanged()
+ .replay(1).autoConnect();
+ /* It might make more sense for b -> mLastBlessings = b to be an onNext before the above
+ replay rather than a subscription (especially if we start getting
+ OnErrorNotImplementedException or have to include a possibly redundant error reporter).
+ However, replay, even with autoConnect(0), does not offer backpressure support unless it has
+ subscribers. We can get around this by adding .onBackpressureBuffer(1), but if this turns
+ out to be a better way of doing this, we should submit an issue requesting that
+ OperatorReplay use its buffer size for backpressure. */
+ mPassiveRxBlessings.subscribe(b -> mLastBlessings = b);
+ mRxBlessings = Observable.defer(() -> FutureConverter.toObservable(refreshBlessings()))
+ .ignoreElements()
+ .concatWith(mPassiveRxBlessings);
+ }
+
+ @Synchronized("mSeekLock")
+ public boolean isAwaitingBlessings() {
+ return mCurrentSeek != null;
+ }
+
+ protected abstract ListenableFuture<Blessings> handleBlessingsRefresh();
+
+ @Synchronized("mSeekLock")
+ private void onBlessingsHandled() {
+ mCurrentSeek = null;
+ }
+
+ @Override
+ @Synchronized("mSeekLock")
+ public ListenableFuture<Blessings> refreshBlessings() {
+ if (isAwaitingBlessings()) {
+ return mCurrentSeek;
+ }
+
+ // Store in a local variable as well in case onBlessingsHandled immediately clears the
+ // Future
+ final ListenableFuture<Blessings> seek = mCurrentSeek = handleBlessingsRefresh();
+ mPub.onNext(seek);
+ seek.addListener(this::onBlessingsHandled, MoreExecutors.directExecutor());
+
+ return seek;
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragment.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragment.java
deleted file mode 100644
index 8e95fa5..0000000
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragment.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.baku.toolkit.blessings;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.content.Intent;
-import android.os.Bundle;
-
-import io.v.android.v23.services.blessing.BlessingService;
-import io.v.baku.toolkit.ErrorReporters;
-import io.v.v23.security.Blessings;
-import lombok.experimental.Accessors;
-import lombok.experimental.Delegate;
-import lombok.extern.slf4j.Slf4j;
-import rx.Observable;
-import rx.subjects.ReplaySubject;
-
-@Accessors(prefix = "m")
-@Slf4j
-public class AccountManagerBlessingsFragment extends Fragment
- implements RefreshableBlessingsProvider {
- public static final String TAG = AccountManagerBlessingsFragment.class.getName();
-
- private static final int BLESSING_REQUEST = 0;
- private static final String SEEKING =
- AccountManagerBlessingsFragment.class.getName() + ".seeking";
-
- public static AccountManagerBlessingsFragment find(final FragmentManager mgr) {
- return (AccountManagerBlessingsFragment)mgr.findFragmentByTag(TAG);
- }
-
- @Delegate(types = RefreshableBlessingsProvider.class, excludes = BlessingsProvider.class)
- private ActivityBlessingsSeeker mSeeker;
- private ReplaySubject<ActivityBlessingsSeeker> mSeekers = ReplaySubject.createWithSize(1);
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final boolean resumingSeek = savedInstanceState != null &&
- savedInstanceState.getBoolean(SEEKING);
- mSeeker = new ActivityBlessingsSeeker(
- getActivity(), ErrorReporters.forFragment(this), resumingSeek) {
- @Override
- protected void seekBlessings() {
- final Intent intent = BlessingService.newBlessingIntent(getActivity());
- startActivityForResult(intent, BLESSING_REQUEST);
- }
- };
- mSeekers.onNext(mSeeker);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SEEKING, mSeeker.isAwaitingBlessings());
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- switch (requestCode) {
- case BLESSING_REQUEST:
- try {
- mSeeker.setBlessings(BlessingsUtils.fromActivityResult(resultCode, data));
- } catch (final Exception e) {
- mSeeker.handleBlessingsError(e);
- }
- break;
- default:
- }
- }
-
- @Override
- public Observable<Blessings> getRxBlessings() {
- return mSeekers.switchMap(ActivityBlessingsSeeker::getRxBlessings);
- }
-
- @Override
- public Observable<Blessings> getPassiveRxBlessings() {
- return mSeekers.switchMap(ActivityBlessingsSeeker::getPassiveRxBlessings);
- }
-}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeeker.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeeker.java
deleted file mode 100644
index 302ef3c..0000000
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeeker.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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.baku.toolkit.blessings;
-
-import android.app.Activity;
-
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
-
-import net.javacrumbs.futureconverter.guavarx.FutureConverter;
-
-import io.v.android.v23.services.blessing.BlessingCreationException;
-import io.v.baku.toolkit.ErrorReporter;
-import io.v.baku.toolkit.R;
-import io.v.v23.security.Blessings;
-import io.v.v23.verror.VException;
-import lombok.Getter;
-import lombok.Synchronized;
-import lombok.experimental.Accessors;
-import lombok.extern.slf4j.Slf4j;
-import rx.Observable;
-import rx.subjects.PublishSubject;
-
-@Accessors(prefix = "m")
-@Slf4j
-public abstract class ActivityBlessingsSeeker implements RefreshableBlessingsProvider {
- /**
- * An observable that, when subscribed to, refreshes the blessing. If the account manager needs
- * to be invoked for the refresh, the subscription will not produce results until the invocation
- * completes. Subsequently, it will receive all blessings refreshed via
- * {@link #refreshBlessings()} and other subscriptions to {@link #getRxBlessings()}.
- */
- @Getter
- private final Observable<Blessings> mRxBlessings;
- /**
- * An observable for the blessings that does not refresh when subscribed to. Upon subscription,
- * this will produce the last known blessing. It will subsequently receive all blessings
- * refreshed via {@link #refreshBlessings()} and subscriptions to {@link #getRxBlessings()}.
- */
- @Getter
- private final Observable<Blessings> mPassiveRxBlessings;
-
- private final Activity mActivity;
- private final ErrorReporter mErrorReporter;
- private final PublishSubject<ListenableFuture<Blessings>> mPub;
- private Blessings mLastBlessings;
- private SettableFuture<Blessings> mCurrentSeek;
- private final Object mSeekLock = new Object();
-
- public ActivityBlessingsSeeker(final Activity activity, final ErrorReporter errorReporter,
- final boolean seekInProgress) {
- mActivity = activity;
- mErrorReporter = errorReporter;
-
- if (seekInProgress) {
- mCurrentSeek = SettableFuture.create();
- }
-
- mPub = PublishSubject.create();
- mPassiveRxBlessings = mPub
- .flatMap(FutureConverter::toObservable)
- .distinctUntilChanged()
- .replay(1).autoConnect();
- /* It might make more sense for b -> mLastBlessings = b to be an onNext before the above
- replay rather than a subscription (especially if we start getting
- OnErrorNotImplementedException or have to include a possibly redundant error reporter).
- However, replay, even with autoConnect(0), does not offer backpressure support unless it has
- subscribers. We can get around this by adding .onBackpressureBuffer(1), but if this turns
- out to be a better way of doing this, we should submit an issue requesting that
- OperatorReplay use its buffer size for backpressure. */
- mPassiveRxBlessings.subscribe(b -> mLastBlessings = b);
- mRxBlessings = Observable.defer(() -> FutureConverter.toObservable(refreshBlessings()))
- .ignoreElements()
- .concatWith(mPassiveRxBlessings);
- }
-
- @Synchronized("mSeekLock")
- public boolean isAwaitingBlessings() {
- return mCurrentSeek != null;
- }
-
- @Override
- @Synchronized("mSeekLock")
- public ListenableFuture<Blessings> refreshBlessings() {
- if (isAwaitingBlessings()) {
- return mCurrentSeek;
- }
-
- Blessings mgrBlessings;
- try {
- mgrBlessings = BlessingsUtils.readSharedPrefs(mActivity.getApplicationContext());
- } catch (final VException e) {
- log.warn("Could not get blessings from shared preferences", e);
- mgrBlessings = null;
- }
-
- final ListenableFuture<Blessings> nextBlessings;
-
- if (mgrBlessings == null) {
- nextBlessings = mCurrentSeek = SettableFuture.create();
- seekBlessings();
- } else {
- nextBlessings = Futures.immediateFuture(mgrBlessings);
- }
- mPub.onNext(nextBlessings);
-
- return nextBlessings;
- }
-
- protected abstract void seekBlessings();
-
- /**
- * It is an error to call this method when this instance is not awaiting blessings.
- */
- public void handleBlessingsError(final Throwable t) {
- if (t instanceof BlessingCreationException) {
- /* This exception can occur if a user hits "Deny" in Blessings Manager, so don't treat
- it as an error if we have a fallback. */
- if (mLastBlessings == null) {
- mErrorReporter.onError(R.string.err_blessings_required, t);
- } else {
- log.warn("Could not create blessings", t);
- }
- } else if (t instanceof VException) {
- mErrorReporter.onError(R.string.err_blessings_decode, t);
- } else {
- mCurrentSeek.setException(t);
- synchronized (mSeekLock) {
- mCurrentSeek = null;
- }
- return;
- }
-
- if (mLastBlessings == null) {
- mActivity.finish();
- /* Block while the app exits, as opposed to returning an error that would be reported
- (redundantly) elsewhere. */
- } else {
- setBlessings(mLastBlessings);
- }
- }
-
- /**
- * It is an error to call this method when this instance is not awaiting blessings.
- */
- public void setBlessings(final Blessings b) {
- try {
- BlessingsUtils.writeSharedPrefs(mActivity.getApplicationContext(), b);
- } catch (final VException e) {
- mErrorReporter.onError(R.string.err_blessings_store, e);
- } finally {
- mCurrentSeek.set(b);
- synchronized (mSeekLock) {
- mCurrentSeek = null;
- }
- }
- }
-}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProvider.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProvider.java
new file mode 100644
index 0000000..9806443
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProvider.java
@@ -0,0 +1,37 @@
+// 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.baku.toolkit.blessings;
+
+import android.app.Activity;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import io.v.android.libs.security.BlessingsManager;
+import io.v.baku.toolkit.VAndroidContextTrait;
+import io.v.v23.context.VContext;
+import io.v.v23.security.Blessings;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class BlessingsManagerBlessingsProvider extends AbstractRefreshableBlessingsProvider {
+ private final VContext mVContext;
+ private final Activity mActivity;
+ private final String mKey;
+ private final boolean mSetAsDefault;
+
+ public BlessingsManagerBlessingsProvider(
+ final VAndroidContextTrait<? extends Activity> activity) {
+ this(activity.getVContext(), activity.getAndroidContext());
+ }
+
+ public BlessingsManagerBlessingsProvider(final VContext vContext, final Activity activity) {
+ this(vContext, activity, BlessingsUtils.PREF_BLESSINGS, false);
+ }
+
+ @Override
+ protected ListenableFuture<Blessings> handleBlessingsRefresh() {
+ return BlessingsManager.getBlessings(mVContext, mActivity, mKey, mSetAsDefault);
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsUtils.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsUtils.java
index 7381b16..3ea41e4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsUtils.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/BlessingsUtils.java
@@ -5,7 +5,6 @@
package io.v.baku.toolkit.blessings;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
@@ -14,6 +13,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
@@ -30,8 +30,6 @@
import java.util.regex.Pattern;
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.impl.google.naming.NamingUtil;
import io.v.v23.context.VContext;
import io.v.v23.security.BlessingPattern;
@@ -62,7 +60,8 @@
public static final String
PREF_BLESSINGS = "VanadiumBlessings",
GLOBAL_BLESSING_ROOT_URL = "https://dev.v.io/auth/blessing-root";
- public static final Pattern DEV_V_IO_USER = Pattern.compile("dev\\.v\\.io:u:([^:]+).*");
+ public static final Pattern DEV_V_IO_CLIENT_USER =
+ Pattern.compile("dev\\.v\\.io:o:([^:]+):([^:]+).*");
public static final AccessList OPEN_ACL = new AccessList(
ImmutableList.of(new BlessingPattern("...")), ImmutableList.of());
@@ -77,12 +76,6 @@
OPEN_DATA_PERMS = dataPermissions(OPEN_ACL),
OPEN_MOUNT_PERMS = mountPermissions(OPEN_ACL);
- public static Blessings fromActivityResult(final int resultCode, final Intent data)
- throws BlessingCreationException, VException {
- // The Account Manager will pass us the blessings to use as an array of bytes.
- return decodeBlessings(BlessingService.extractBlessingReply(resultCode, data));
- }
-
public static void writeSharedPrefs(final Context context, final Blessings blessings)
throws VException {
writeSharedPrefs(PreferenceManager.getDefaultSharedPreferences(context), PREF_BLESSINGS,
@@ -124,34 +117,51 @@
ImmutableList.of());
}
- public static Stream<String> blessingsToUsernameStream(final VContext ctx,
- final Blessings blessings) {
+ /**
+ * This method adds the given {@link BlessingPattern} to the given {@link AccessList} but does
+ * not perform deduping or checking to make sure the new pattern is not in
+ * {@link AccessList#getNotIn()}.
+ */
+ public static AccessList augmentAcl(final AccessList acl, final BlessingPattern newBlessing) {
+ return new AccessList(ImmutableList.<BlessingPattern>builder()
+ .addAll(acl.getIn())
+ .add(newBlessing).build(),
+ ImmutableList.of());
+ }
+
+ public static Stream<ClientUser> blessingsToClientUserStream(final VContext ctx,
+ final Blessings blessings) {
return StreamSupport.stream(getBlessingNames(ctx, blessings))
- .map(DEV_V_IO_USER::matcher)
+ .map(DEV_V_IO_CLIENT_USER::matcher)
.filter(Matcher::matches)
- .map(m -> m.group(1));
+ .map(m -> new ClientUser(m.group(1), m.group(2)));
}
/**
- * This method finds and parses all blessings of the form dev.v.io/u/.... This is different from
- * the method at https://v.io/tutorials/java/android.html, which can return additional
- * extensions ("/android").
+ * This method finds and parses all blessings of the form dev.v.io/o/....
*/
- public static Set<String> blessingsToUsernames(final VContext ctx, final Blessings blessings) {
- return blessingsToUsernameStream(ctx, blessings).collect(Collectors.toSet());
+ public static Set<ClientUser> blessingsToClientUsers(
+ final VContext ctx, final Blessings blessings) {
+ return blessingsToClientUserStream(ctx, blessings).collect(Collectors.toSet());
}
public static String userMount(final String username) {
return NamingUtil.join("users", username);
}
- public static Stream<String> blessingsToUserMountStream(final VContext ctx, final Blessings blessings) {
- return blessingsToUsernameStream(ctx, blessings)
- .map(BlessingsUtils::userMount);
+ public static String clientMount(final String clientId) {
+ return NamingUtil.join("tmp", "clients", clientId);
}
- public static Set<String> blessingsToUserMounts(final VContext ctx, final Blessings blessings) {
- return blessingsToUserMountStream(ctx, blessings).collect(Collectors.toSet());
+ public static Stream<String> blessingsToClientMountStream(final VContext ctx,
+ final Blessings blessings) {
+ return blessingsToClientUserStream(ctx, blessings)
+ .map(cu -> BlessingsUtils.clientMount(cu.getClientId()));
+ }
+
+ public static Set<String> blessingsToClientMounts(final VContext ctx,
+ final Blessings blessings) {
+ return blessingsToClientMountStream(ctx, blessings).collect(Collectors.toSet());
}
public static Permissions homogeneousPermissions(final Set<Tag> tags, final AccessList acl) {
@@ -171,6 +181,22 @@
}
/**
+ * TODO(rosswang): This probably won't be best practice in the long run, but we'll need it until
+ * we can bless the cloud Syncbase instance remotely.
+ */
+ public static Permissions cloudSyngroupPermissions(final AccessList userAcl,
+ final BlessingPattern sgHostBlessing) {
+ final AccessList cloudAcl = augmentAcl(userAcl, sgHostBlessing);
+ return new Permissions(ImmutableMap.of(
+ Constants.ADMIN.getValue(), cloudAcl,
+ Constants.READ.getValue(), cloudAcl,
+ Constants.WRITE.getValue(), userAcl,
+ Constants.RESOLVE.getValue(), userAcl,
+ Constants.DEBUG.getValue(), userAcl
+ ));
+ }
+
+ /**
* Standard blessing handling for Vanadium applications:
* <ul>
* <li>Provide the given blessings when anybody connects to us.</li>
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ClientUser.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ClientUser.java
new file mode 100644
index 0000000..af0f44b
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/blessings/ClientUser.java
@@ -0,0 +1,14 @@
+// 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.baku.toolkit.blessings;
+
+import lombok.Value;
+import lombok.experimental.Accessors;
+
+@Value
+@Accessors(prefix = "m")
+public class ClientUser {
+ String mClientId, mUsername;
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/debug/DebugUtils.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/debug/DebugUtils.java
index 1d4ce86..56bf749 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/debug/DebugUtils.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/debug/DebugUtils.java
@@ -13,7 +13,6 @@
import java.io.IOException;
-import io.v.android.v23.V;
import io.v.baku.toolkit.R;
import io.v.baku.toolkit.VAndroidContextTrait;
import lombok.experimental.UtilityClass;
@@ -79,7 +78,6 @@
*/
public static void killProcess(final Context context) {
stopPackageServices(context);
- V.shutdown();
System.exit(0);
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/package-info.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/package-info.java
new file mode 100644
index 0000000..83e196e
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/package-info.java
@@ -0,0 +1,44 @@
+// 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.
+
+/**
+ * This package is the entry point into the Baku Toolkit. The easiest way for an application to take
+ * advantage of Baku is for its activities with distributed state to inherit from
+ * {@link io.v.baku.toolkit.BakuActivity} (or {@link io.v.baku.toolkit.BakuAppCompatActivity}; see
+ * {@link io.v.baku.toolkit.BakuActivityMixin} for custom inheritance trees). Then, for any UI
+ * widget that should have distributed state, the client application should build data bindings by
+ * chaining methods from a {@link io.v.baku.toolkit.BakuActivityMixin#binder() binder()} call,
+ * binding shared data fields to UI widget properties. For <a href="https://goo.gl/P0Ag9a"
+ * target="_blank">example</a>, the following binds a data key named {@code "text"} to the text of a
+ * {@link android.widget.TextView} with ID {@code textView}:
+ * <pre><code>
+ * @Override
+ * protected void onCreate(final Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * setContentView(R.layout.activity_layout);
+ *
+ * binder().key("text")
+ * .bindTo(R.id.textView);
+ * }
+ * }
+ * </code></pre>
+ * Collection bindings (from vector data to list/recycler views) are similarly exposed through a
+ * {@link io.v.baku.toolkit.BakuActivityMixin#collectionBinder() collectionBinder()} builder. Writes
+ * can be performed directly via {@link io.v.baku.toolkit.syncbase.BakuTable#put(java.lang.String,
+ * java.lang.Object) getSyncbaseTable().put(key, value)}. More information about data bindings is
+ * available in the {@link io.v.baku.toolkit.bind} package documentation.
+ * <p>
+ * The Baku Toolkit creates a Syncbase table to use by default for data binding, and creates and
+ * manages a default {@linkplain io.v.rx.syncbase.UserCloudSyncgroup global user-level cloud
+ * syncgroup} to sync distributed data across all instances of the application belonging to a user.
+ * <p>
+ * Baku components are built in layers bundling common sets of functionality. This allows
+ * application developers the flexibility to selectively interact with APIs when they need to work
+ * around our high-level abstractions which potentially don't meet their use cases.
+ * <p>
+ * Sample code is available in the
+ * <a href="https://vanadium.googlesource.com/release.projects.baku/" target="_blank">baku projects
+ * repo</a>.
+ */
+package io.v.baku.toolkit;
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/BakuSyncbase.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/BakuSyncbase.java
index 4ceec0f..b38ad0f 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/BakuSyncbase.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/BakuSyncbase.java
@@ -5,13 +5,13 @@
package io.v.baku.toolkit.syncbase;
import io.v.baku.toolkit.BakuActivityTrait;
-import io.v.rx.syncbase.RxSyncbase;
+import io.v.rx.syncbase.RxAndroidSyncbase;
import lombok.Getter;
import lombok.experimental.Accessors;
@Accessors(prefix = "m")
@Getter
-public class BakuSyncbase extends RxSyncbase {
+public class BakuSyncbase extends RxAndroidSyncbase {
private final BakuActivityTrait<?> mBakuContext;
public BakuSyncbase(final BakuActivityTrait bakuContext) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/package-info.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/package-info.java
index 26d08b8..f9f2953 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/package-info.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/syncbase/package-info.java
@@ -3,8 +3,8 @@
// license that can be found in the LICENSE file.
/**
- * For each {@link io.v.rx.syncbase.SyncbaseEntity RxSyncbase entity} class, this package adds a
- * {@link io.v.baku.toolkit.BakuActivityTrait} as context information from which to derive other
+ * For each {@linkplain io.v.rx.syncbase.SyncbaseEntity RxSyncbase entity} class, this package adds
+ * a {@link io.v.baku.toolkit.BakuActivityTrait} as context information from which to derive other
* dependencies and to use for error handling. This allows Baku client code to use Syncbase more
* easily, without needing to explicitly subscribe error handlers for every operation. For more
* information, see {@link io.v.baku.toolkit.syncbase.BakuTable#exec(rx.functions.Func1)}.
diff --git a/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseClient.java b/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidClient.java
similarity index 92%
rename from baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseClient.java
rename to baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidClient.java
index 492b5cd..653aa64 100644
--- a/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseClient.java
+++ b/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidClient.java
@@ -30,7 +30,7 @@
* {@link Observable}.
*/
@Accessors(prefix = "m")
-public class SyncbaseClient implements AutoCloseable {
+public class SyncbaseAndroidClient implements AutoCloseable {
public static class BindException extends Exception {
public BindException(final String message) {
super(message);
@@ -100,8 +100,8 @@
* will not be started until blessings are available.
* TODO(rosswang): this should either handle blessings changes or not care.
*/
- public SyncbaseClient(final Context androidContext, final Observable<Blessings> rxBlessings,
- final boolean cleanStart, final Duration keepAlive) {
+ public SyncbaseAndroidClient(final Context androidContext, final Observable<Blessings> rxBlessings,
+ final boolean cleanStart, final Duration keepAlive) {
mAndroidContext = androidContext;
/*
@@ -120,7 +120,7 @@
if (rxBlessings == null) {
startService(androidContext, intent);
} else {
- rxBlessings.first()
+ rxBlessings.take(1)
.subscribe(s -> {
startService(androidContext, intent);
}, rpl::onError);
@@ -129,7 +129,7 @@
mObservable = rpl.filter(s -> s != null);
}
- public SyncbaseClient(final Context androidContext, final Observable<Blessings> rxBlessings) {
+ public SyncbaseAndroidClient(final Context androidContext, final Observable<Blessings> rxBlessings) {
this(androidContext, rxBlessings, false, null);
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidService.java b/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidService.java
index 4edcc83..142db3f 100644
--- a/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidService.java
+++ b/baku-toolkit/lib/src/main/java/io/v/debug/SyncbaseAndroidService.java
@@ -81,7 +81,7 @@
try {
mObservable.doOnNext(VFn.unchecked(b -> {
log.info("Stopping Syncbase");
- b.mServer.stop();
+ mVContext.cancel();
}))
.timeout(STOP_TIMEOUT.getMillis(), TimeUnit.MILLISECONDS)
.toBlocking()
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/ClientLevelCloudSync.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/ClientLevelCloudSync.java
new file mode 100644
index 0000000..d8e3e92
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/ClientLevelCloudSync.java
@@ -0,0 +1,32 @@
+// 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.rx.syncbase;
+
+import java.util.Arrays;
+import java.util.List;
+
+import io.v.baku.toolkit.blessings.BlessingsUtils;
+import io.v.baku.toolkit.blessings.ClientUser;
+import io.v.impl.google.naming.NamingUtil;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class ClientLevelCloudSync implements SyncHostLevel {
+ public static final ClientLevelCloudSync DEFAULT =
+ new ClientLevelCloudSync(DEFAULT_CLOUD_SYNC_SUFFIX, DEFAULT_RENDEZVOUS_SUFFIX);
+
+ private final String mSgHostSuffix, mRendezvousSuffix;
+
+ @Override
+ public String getSyncgroupHostName(final ClientUser clientUser) {
+ return NamingUtil.join(BlessingsUtils.clientMount(clientUser.getClientId()), mSgHostSuffix);
+ }
+
+ @Override
+ public List<String> getRendezvousTableNames(final ClientUser clientUser) {
+ return Arrays.asList(NamingUtil.join(
+ BlessingsUtils.clientMount(clientUser.getClientId()), mRendezvousSuffix));
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java
deleted file mode 100644
index 7bafee7..0000000
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java
+++ /dev/null
@@ -1,191 +0,0 @@
-// 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.rx.syncbase;
-
-import android.content.Context;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-import io.v.baku.toolkit.BakuActivityTrait;
-import io.v.baku.toolkit.R;
-import io.v.baku.toolkit.VAndroidContextTrait;
-import io.v.baku.toolkit.blessings.BlessingsUtils;
-import io.v.v23.context.VContext;
-import io.v.v23.security.Blessings;
-import io.v.v23.security.access.AccessList;
-import io.v.v23.security.access.Permissions;
-import io.v.v23.services.syncbase.nosql.SyncgroupJoinFailedException;
-import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
-import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
-import io.v.v23.services.syncbase.nosql.TableRow;
-import io.v.v23.syncbase.nosql.Database;
-import io.v.v23.syncbase.nosql.Syncgroup;
-import java8.util.function.Function;
-import java8.util.stream.Collectors;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.experimental.Accessors;
-import lombok.extern.slf4j.Slf4j;
-import rx.Observable;
-import rx.Subscription;
-import rx.functions.Action2;
-
-import static net.javacrumbs.futureconverter.guavarx.FutureConverter.toObservable;
-
-@Accessors(prefix = "m")
-@AllArgsConstructor
-@Builder(builderClassName = "Builder")
-@Slf4j
-public class GlobalUserSyncgroup {
- public static final String
- DEFAULT_SYNCGROUP_HOST_NAME = "usersync",
- DEFAULT_SYNCGROUP_SUFFIX = "user",
- DEFAULT_RENDEZVOUS_MOUNT = "sgmt";
- public static final SyncgroupMemberInfo
- DEFAULT_SYNCGROUP_MEMBER_INFO = new SyncgroupMemberInfo();
-
- public static GlobalUserSyncgroup forActivity(final BakuActivityTrait t) {
- return builder().activity(t).build();
- }
-
- public static class Builder {
- private String sgSuffix = DEFAULT_SYNCGROUP_SUFFIX;
- private Function<String, String> descriptionForUsername = u -> "User syncgroup for " + u;
- private Function<AccessList, Permissions> permissionsForUserAcl =
- BlessingsUtils::syncgroupPermissions;
- private List<TableRow> prefixes = new ArrayList<>();
- private SyncgroupMemberInfo memberInfo = DEFAULT_SYNCGROUP_MEMBER_INFO;
-
- /**
- * This is an additive setter to {@link #prefixes(List)}.
- */
- public Builder prefix(final TableRow prefix) {
- prefixes.add(prefix);
- return this;
- }
-
- /**
- * This is an additive setter to {@link #prefixes(List)}.
- */
- public Builder prefix(final String tableName, final String rowPrefix) {
- return prefix(new TableRow(tableName, rowPrefix));
- }
-
- /**
- * This is an additive setter to {@link #prefixes(List)}.
- */
- public Builder prefix(final String tableName) {
- return prefix(tableName, "");
- }
-
- /**
- * This is a composite setter for:
- * <ul>
- * <li>{@code vContext}</li>
- * <li>{@code rxBlessings}</li>
- * <li>{@code syncHostLevel}</li> (as a new
- * {@link UserAppSyncHost#UserAppSyncHost(Context)})
- * <li>{@code onError}</li>
- * </ul>
- * and should be called prior to any overrides for those fields.
- */
- public Builder activity(final VAndroidContextTrait<?> t) {
- return vContext(t.getVContext())
- .rxBlessings(t.getBlessingsProvider().getRxBlessings())
- .syncHostLevel(new UserAppSyncHost(t.getAndroidContext()))
- .onError(t.getErrorReporter()::onError);
- }
-
- /**
- * In addition to those fields in {@link #activity(VAndroidContextTrait)}, this
- * additionally sets:
- * <ul>
- * <li>{@code syncbase}</li>
- * <li>{@code db}</li>
- * <li>and adds to {@code prefixes}</li>
- * </ul>
- */
- public Builder activity(final BakuActivityTrait<?> t) {
- return activity(t.getVAndroidContextTrait())
- .syncbase(t.getSyncbase())
- .db(t.getSyncbaseDb())
- .prefix(t.getSyncbaseTableName());
- }
- }
-
- private final VContext mVContext;
- private final Observable<Blessings> mRxBlessings;
- private final SyncHostLevel mSyncHostLevel;
- private final String mSgSuffix;
- private final RxSyncbase mSyncbase;
- private final RxDb mDb;
- private final Function<String, String> mDescriptionForUsername;
- private final Function<AccessList, Permissions> mPermissionsForUserAcl;
- private final List<TableRow> mPrefixes;
- private final SyncgroupMemberInfo mMemberInfo;
- /**
- * @see io.v.baku.toolkit.ErrorReporter#onError(int, Throwable)
- */
- private final Action2<Integer, Throwable> mOnError;
-
- private SyncgroupSpec createSpec(final String username, final AccessList userAcl) {
- return new SyncgroupSpec(mDescriptionForUsername.apply(username),
- mPermissionsForUserAcl.apply(userAcl), mPrefixes,
- mSyncHostLevel.getRendezvousTableNames(username), false);
- }
-
- private Observable<SyncgroupSpec> createOrJoinSyncgroup(final Database db, final String sgName,
- final SyncgroupSpec spec) {
- final Syncgroup sg = db.getSyncgroup(sgName);
- return Observable.defer(() -> toObservable(sg.join(mVContext, mMemberInfo)))
- .doOnCompleted(() -> log.info("Joined syncgroup " + sgName))
- .onErrorResumeNext(t -> t instanceof SyncgroupJoinFailedException ?
- toObservable(sg.create(mVContext, spec, mMemberInfo))
- .doOnCompleted(() -> log.info("Created syncgroup " + sgName))
- .map(x -> spec) :
- Observable.error(t));
- }
-
- private Observable<Object> createOrJoinSyncgroup(final String username, final AccessList acl) {
- final String sgHost = mSyncHostLevel.getSyncgroupHostName(username);
- final String sgName = RxSyncbase.syncgroupName(sgHost, mSgSuffix);
- final SyncgroupSpec spec = createSpec(username, acl);
-
- final Observable<Object> mount = SgHostUtil.ensureSyncgroupHost(
- mVContext, mSyncbase.getRxServer(), sgHost).share();
-
- return mDb.getObservable()
- .switchMap(db -> Observable.merge(mount.first().ignoreElements().concatWith(
- createOrJoinSyncgroup(db, sgName, spec)), mount));
- }
-
- public Observable<?> rxJoin() {
- return Observable.switchOnNext(mRxBlessings
- .map(b -> {
- final AccessList acl = BlessingsUtils.blessingsToAcl(mVContext, b);
- final List<Observable<?>> createOrJoins =
- BlessingsUtils.blessingsToUsernameStream(mVContext, b)
- .distinct()
- .map(u -> createOrJoinSyncgroup(u, acl))
- .collect(Collectors.toList());
- if (createOrJoins.isEmpty()) {
- throw new NoSuchElementException("GlobalUserSyncgroup requires a " +
- "username; no username blessings found. Blessings: " + b);
- }
- return Observable.merge(createOrJoins);
- }));
- }
-
- /**
- * It is not generally necessary to unsubscribe explicitly from this subscription since the
- * lifecycle of the Syncbase client is generally tied to a Baku Activity.
- */
- public Subscription join() {
- return rxJoin().subscribe(x -> {
- }, t -> mOnError.call(R.string.err_syncgroup_join, t));
- }
-}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxAndroidSyncbase.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxAndroidSyncbase.java
new file mode 100644
index 0000000..120f97b
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxAndroidSyncbase.java
@@ -0,0 +1,55 @@
+// 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.rx.syncbase;
+
+import android.content.Context;
+
+import io.v.baku.toolkit.VAndroidContextTrait;
+import io.v.debug.SyncbaseAndroidClient;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.Server;
+import io.v.v23.security.Blessings;
+import io.v.v23.syncbase.SyncbaseService;
+import lombok.experimental.Accessors;
+import rx.Observable;
+
+/**
+ * Models a binding to a Syncbase Android service as an {@code Observable} of
+ * {@link SyncbaseService}s. The binding will be asynchronously made and then potentially
+ * periodically lost and regained, so modeling further operations as subscriptions works well.
+ */
+@Accessors(prefix = "m")
+public class RxAndroidSyncbase extends RxSyncbase implements AutoCloseable {
+ private final SyncbaseAndroidClient mClient;
+
+ public Observable<Server> getRxServer() {
+ return mClient.getRxServer();
+ }
+
+ @Override
+ public Observable<SyncbaseService> getRxClient() {
+ return mClient.getRxClient();
+ }
+
+ public RxAndroidSyncbase(final VContext vContext, final SyncbaseAndroidClient client) {
+ super(vContext);
+ mClient = client;
+ }
+
+ public RxAndroidSyncbase(final Context androidContext, final VContext ctx,
+ final Observable<Blessings> rxBlessings) {
+ this(ctx, new SyncbaseAndroidClient(androidContext, rxBlessings));
+ }
+
+ public RxAndroidSyncbase(final VAndroidContextTrait trait) {
+ this(trait.getAndroidContext(), trait.getVContext(),
+ trait.getBlessingsProvider().getRxBlessings());
+ }
+
+ @Override
+ public void close() {
+ mClient.close();
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxApp.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxApp.java
index 9291593..9676387 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxApp.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxApp.java
@@ -40,9 +40,7 @@
@Override
public Observable<SyncbaseApp> mapFrom(final SyncbaseService sb) {
final SyncbaseApp app = sb.getApp(mName);
- return toObservable(SyncbaseEntity.compose(app::exists, app::create)
- .ensureExists(mVContext, null))
- .map(x -> app);
+ return toObservable(SyncbaseEntity.forApp(app).ensureExists(mVContext)).map(x -> app);
}
public RxDb rxDb(final String name) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxDb.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxDb.java
index 7d89b27..2fcf8a0 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxDb.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxDb.java
@@ -40,9 +40,7 @@
@Override
public Observable<Database> mapFrom(final SyncbaseApp app) {
final Database db = app.getNoSqlDatabase(mName, null);
- return toObservable(SyncbaseEntity.compose(db::exists, db::create)
- .ensureExists(mVContext, null))
- .map(x -> db);
+ return toObservable(SyncbaseEntity.forDb(db).ensureExists(mVContext)).map(x -> db);
}
public RxTable rxTable(final String name) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncbase.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncbase.java
index e441be4..00ff353 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncbase.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncbase.java
@@ -4,57 +4,47 @@
package io.v.rx.syncbase;
-import android.content.Context;
-
-import io.v.baku.toolkit.VAndroidContextTrait;
-import io.v.debug.SyncbaseClient;
import io.v.impl.google.naming.NamingUtil;
import io.v.v23.context.VContext;
-import io.v.v23.rpc.Server;
-import io.v.v23.security.Blessings;
+import io.v.v23.syncbase.Syncbase;
import io.v.v23.syncbase.SyncbaseService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;
import rx.Observable;
-/**
- * Models a binding to a Syncbase Android service as an {@code Observable} of
- * {@link SyncbaseService}s. The binding will be asynchronously made and then potentially
- * periodically lost and regained, so modeling further operations as subscriptions works well.
- */
@Accessors(prefix = "m")
@AllArgsConstructor
-public class RxSyncbase implements AutoCloseable {
+public abstract class RxSyncbase {
public static String syncgroupName(final String sgHost, final String sgSuffix) {
return NamingUtil.join(sgHost, "%%sync", sgSuffix);
}
- @Getter private final VContext mVContext;
- private final SyncbaseClient mClient;
-
- public Observable<Server> getRxServer() {
- return mClient.getRxServer();
+ /**
+ * The {@link RxSyncbase#getRxClient()} produced by this factory method will produce exactly
+ * one {@link SyncbaseService}.
+ */
+ public static RxSyncbase fromSyncbaseService(final VContext vContext,
+ final SyncbaseService sb) {
+ return new RxSyncbase(vContext) {
+ @Override
+ public Observable<SyncbaseService> getRxClient() {
+ return Observable.just(sb);
+ }
+ };
}
- public Observable<SyncbaseService> getRxClient() {
- return mClient.getRxClient();
+ /**
+ * @see #fromSyncbaseService(VContext, SyncbaseService)
+ */
+ public static RxSyncbase fromSyncbaseAt(final VContext vContext, final String name) {
+ return fromSyncbaseService(vContext, Syncbase.newService(name));
}
- public RxSyncbase(final Context androidContext, final VContext ctx,
- final Observable<Blessings> rxBlessings) {
- mVContext = ctx;
- mClient = new SyncbaseClient(androidContext, rxBlessings);
- }
+ @Getter
+ private final VContext mVContext;
- public RxSyncbase(final VAndroidContextTrait trait) {
- this(trait.getAndroidContext(), trait.getVContext(),
- trait.getBlessingsProvider().getRxBlessings());
- }
-
- public void close() {
- mClient.close();
- }
+ public abstract Observable<SyncbaseService> getRxClient();
public RxApp rxApp(final String name) {
return new RxApp(name, this);
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncgroup.java
new file mode 100644
index 0000000..c645c79
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxSyncgroup.java
@@ -0,0 +1,36 @@
+// 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.rx.syncbase;
+
+import io.v.baku.toolkit.R;
+import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
+import lombok.AllArgsConstructor;
+import lombok.experimental.Accessors;
+import rx.Observable;
+import rx.Subscription;
+import rx.functions.Action2;
+
+@Accessors(prefix = "m")
+@AllArgsConstructor
+public abstract class RxSyncgroup {
+ public static final SyncgroupMemberInfo
+ DEFAULT_SYNCGROUP_MEMBER_INFO = new SyncgroupMemberInfo();
+
+ /**
+ * @see io.v.baku.toolkit.ErrorReporter#onError(int, Throwable)
+ */
+ protected final Action2<Integer, Throwable> mOnError;
+
+ public abstract Observable<?> rxJoin();
+
+ /**
+ * It is not generally necessary to unsubscribe explicitly from this subscription since the
+ * lifecycle of the Syncbase client is generally tied to a Baku Activity.
+ */
+ public Subscription join() {
+ return rxJoin().subscribe(x -> {
+ }, t -> mOnError.call(R.string.err_syncgroup_join, t));
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
index ba7e941..2aa9e0a 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
@@ -16,7 +16,6 @@
import io.v.rx.RxInputChannel;
import io.v.rx.VFn;
import io.v.v23.InputChannel;
-import io.v.v23.context.CancelableVContext;
import io.v.v23.context.VContext;
import io.v.v23.services.syncbase.nosql.BatchOptions;
import io.v.v23.services.syncbase.nosql.KeyValue;
@@ -87,9 +86,7 @@
@Override
public Observable<Table> mapFrom(final DatabaseCore db) {
final Table t = db.getTable(mName);
- return toObservable(SyncbaseEntity.compose(t::exists, t::create)
- .ensureExists(mVContext, null))
- .map(x -> t);
+ return toObservable(SyncbaseEntity.forTable(t).ensureExists(mVContext)).map(x -> t);
}
private <T> Observable<T> getInitial(
@@ -212,7 +209,7 @@
}
private void cancelContextOnDisconnect(final Subscriber<?> subscriber,
- final CancelableVContext cancelable,
+ final VContext cancelable,
final String prefix) {
subscriber.add(Subscriptions.create(() -> {
log.debug("Cancelling watch on {}: {}", mName, prefix);
@@ -239,7 +236,7 @@
"Unable to abort watch initial read query", t))), r));
})
.switchMap(i -> {
- final CancelableVContext cancelable = mVContext.withCancel();
+ final VContext cancelable = mVContext.withCancel();
cancelContextOnDisconnect(subscriber, cancelable, prefix);
log.debug("Watching {}: {}", mName, prefix);
return mergeInitial.call(i, observeWatchStream.call(
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgHostUtil.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgHostUtil.java
index ef952b4..b895f9f 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgHostUtil.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgHostUtil.java
@@ -4,6 +4,8 @@
package io.v.rx.syncbase;
+import com.google.common.collect.Iterables;
+
import org.joda.time.Duration;
import io.v.rx.MountEvent;
@@ -11,6 +13,7 @@
import io.v.v23.context.VContext;
import io.v.v23.rpc.Server;
import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.nosql.Database;
import io.v.v23.verror.TimeoutException;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@@ -87,4 +90,19 @@
return rxServer.switchMap(s -> isSyncbaseOnline(vContext, name)
.flatMap(online -> online ? Observable.just(0) : mountSgHost(rxServer, name)));
}
+
+ /**
+ * @return an observable that echos the db after the each db emitted by
+ * {@link RxDb#getObservable()} has been ensured to possess the given table names. Upon
+ * subscription, for each db emitted, the observable will create these app/db/table hierarchies
+ * if not already present.
+ */
+ public static Observable<Database> ensureSyncgroupHierarchies(
+ final RxDb rxDb, final Iterable<String> tableNames) {
+ return rxDb.getObservable().switchMap(db -> Observable.merge(Iterables.transform(tableNames,
+ t -> rxDb.rxTable(t)
+ .mapFrom(db)
+ .map(rxt -> db)))
+ .lastOrDefault(db));
+ }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java
new file mode 100644
index 0000000..970d315
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormat.java
@@ -0,0 +1,12 @@
+// 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.rx.syncbase;
+
+import java8.lang.FunctionalInterface;
+
+@FunctionalInterface
+public interface SgSuffixFormat<T> {
+ String get(final T parameters);
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java
new file mode 100644
index 0000000..38eac86
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SgSuffixFormats.java
@@ -0,0 +1,32 @@
+// 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.rx.syncbase;
+
+import com.google.common.escape.CharEscaperBuilder;
+import com.google.common.escape.Escaper;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class SgSuffixFormats {
+ public static final String SG_SUFFIX_DELIMITER = "/";
+ public static final Escaper SG_SUFFIX_COMPONENT_ESCAPER = new CharEscaperBuilder()
+ .addEscape('/', "\\/")
+ .addEscape('\\', "\\\\")
+ .toEscaper();
+
+ public static SgSuffixFormat<Object> simple(final String sgSuffix) {
+ return x -> sgSuffix;
+ }
+
+ public static SgSuffixFormat<UserSyncgroup.Parameters> discriminated(
+ final String customSuffix) {
+ return p -> SG_SUFFIX_COMPONENT_ESCAPER.escape(p.getDb().getRxApp().getName()) +
+ SG_SUFFIX_DELIMITER +
+ SG_SUFFIX_COMPONENT_ESCAPER.escape(p.getDb().getName()) +
+ SG_SUFFIX_DELIMITER +
+ customSuffix;
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncHostLevel.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncHostLevel.java
index 260609e..69eff0d 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncHostLevel.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncHostLevel.java
@@ -6,9 +6,13 @@
import java.util.List;
-public interface SyncHostLevel {
- String DEFAULT_SG_HOST_SUFFIX = "sghost", DEFAULT_RENDEZVOUS_SUFFIX = "sgmt";
+import io.v.baku.toolkit.blessings.ClientUser;
- String getSyncgroupHostName(String username);
- List<String> getRendezvousTableNames(String username);
+public interface SyncHostLevel {
+ String DEFAULT_CLOUD_SYNC_SUFFIX = "cloudsync",
+ DEFAULT_SG_HOST_SUFFIX = "sghost",
+ DEFAULT_RENDEZVOUS_SUFFIX = "sgmt";
+
+ String getSyncgroupHostName(ClientUser clientUser);
+ List<String> getRendezvousTableNames(ClientUser clientUser);
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncbaseEntity.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncbaseEntity.java
index baa13d7..5d672c4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncbaseEntity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SyncbaseEntity.java
@@ -4,15 +4,22 @@
package io.v.rx.syncbase;
+import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import org.robotninjas.concurrent.FluentFuture;
+import org.robotninjas.concurrent.FluentFutures;
+
import io.v.v23.context.VContext;
import io.v.v23.security.access.Permissions;
+import io.v.v23.syncbase.SyncbaseApp;
+import io.v.v23.syncbase.nosql.Database;
+import io.v.v23.syncbase.nosql.Table;
import io.v.v23.verror.ExistException;
-abstract class SyncbaseEntity implements ExistenceAware, Creatable {
+public abstract class SyncbaseEntity implements ExistenceAware, Creatable {
public static SyncbaseEntity compose(final ExistenceAware fnExists, final Creatable fnCreate) {
return new SyncbaseEntity() {
@Override
@@ -27,17 +34,45 @@
};
}
+ public static SyncbaseEntity forApp(final SyncbaseApp app) {
+ return compose(app::exists, app::create);
+ }
+
+ public static SyncbaseEntity forDb(final Database db) {
+ return compose(db::exists, db::create);
+ }
+
+ public static SyncbaseEntity forTable(final Table table) {
+ return compose(table::exists, table::create);
+ }
+
+ private SyncbaseEntity(){}
+
/**
* Utility for Syncbase entities with lazy creation semantics. It would be great if this were
* instead factored into a V23 interface and utility.
+ *
+ * @return a future that completes with {@code true} if this call created the entity,
+ * {@code false} if the entity already existed, or fails if an unexpected error occurred.
*/
- public ListenableFuture<Void> ensureExists(final VContext vContext,
- final Permissions permissions) {
- return Futures.transform(exists(vContext), (AsyncFunction<Boolean, Void>) (e -> e ?
- Futures.immediateFuture(null) :
- Futures.withFallback(create(vContext, permissions), t ->
- t instanceof ExistException ?
- Futures.immediateFuture(null) : Futures.immediateFailedFuture(t))
- ));
+ public FluentFuture<Boolean> ensureExists(final VContext vContext,
+ final Permissions permissions) {
+ return FluentFutures.from(exists(vContext))
+ .transform((AsyncFunction<Boolean, Boolean>) (e -> e ?
+ Futures.immediateFuture(false) :
+ FluentFutures.from(create(vContext, permissions))
+ .transform((Function<Void, Boolean>) (x -> true))
+ .withFallback(t -> t instanceof ExistException ?
+ Futures.<Boolean>immediateFuture(false) :
+ Futures.<Boolean>immediateFailedFuture(t))
+ ));
+ }
+
+ /**
+ * Equivalent to calling {@link #ensureExists(VContext, Permissions)} with null permissions,
+ * inheriting permissions from the hierarchy.
+ */
+ public FluentFuture<Boolean> ensureExists(final VContext vContext) {
+ return ensureExists(vContext, null);
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserAppSyncHost.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserAppSyncHost.java
index 1fa2fce..921ae72 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserAppSyncHost.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserAppSyncHost.java
@@ -10,6 +10,7 @@
import java.util.List;
import io.v.baku.toolkit.blessings.BlessingsUtils;
+import io.v.baku.toolkit.blessings.ClientUser;
import io.v.impl.google.naming.NamingUtil;
import lombok.RequiredArgsConstructor;
@@ -26,13 +27,14 @@
}
@Override
- public String getSyncgroupHostName(final String username) {
- return NamingUtil.join(BlessingsUtils.userMount(username), mAppName, mSgHostSuffix);
+ public String getSyncgroupHostName(final ClientUser clientUser) {
+ return NamingUtil.join(BlessingsUtils.userMount(clientUser.getUsername()),
+ mAppName, mSgHostSuffix);
}
@Override
- public List<String> getRendezvousTableNames(String username) {
+ public List<String> getRendezvousTableNames(final ClientUser clientUser) {
return Arrays.asList(NamingUtil.join(
- BlessingsUtils.userMount(username), mAppName, mRendezvousSuffix));
+ BlessingsUtils.userMount(clientUser.getUsername()), mAppName, mRendezvousSuffix));
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserCloudSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserCloudSyncgroup.java
new file mode 100644
index 0000000..b29ba4c
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserCloudSyncgroup.java
@@ -0,0 +1,85 @@
+// 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.rx.syncbase;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import io.v.baku.toolkit.BakuActivityTrait;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
+import io.v.v23.services.syncbase.nosql.TableRow;
+import io.v.v23.syncbase.nosql.Syncgroup;
+import io.v.v23.verror.ExistException;
+import lombok.extern.slf4j.Slf4j;
+import rx.Observable;
+
+import static net.javacrumbs.futureconverter.guavarx.FutureConverter.toObservable;
+
+@Slf4j
+public class UserCloudSyncgroup extends UserSyncgroup {
+ /**
+ * TODO(rosswang): seriously very temporary!
+ */
+ public static BlessingPattern DEBUG_SG_HOST_BLESSING =
+ new BlessingPattern("dev.v.io:u:rosswang@google.com:app");
+
+ public static UserCloudSyncgroup forActivity(final BakuActivityTrait t) {
+ return builder().activity(t).buildCloud();
+ }
+
+ public UserCloudSyncgroup(final Parameters params) {
+ super(params);
+ }
+
+ private Observable<Void> ensureSyncgroup(final String sgHost, final String sgName,
+ final SyncgroupSpec spec) {
+ // We need app/db/table to sync even on the cloud.
+ // https://github.com/vanadium/issues/issues/857
+ // Use idempotent APIs to allow failure recovery and avoid race conditions. Most of the
+ // time, we'll just short-circuit and join the syncgroup from the get-go.
+ final RxDb remoteDb = RxSyncbase.fromSyncbaseAt(mParams.getVContext(), sgHost)
+ .rxApp(mParams.getDb().getRxApp().getName())
+ .rxDb(mParams.getDb().getName());
+ final List<String> tableNames =
+ Lists.transform(mParams.getPrefixes(), TableRow::getTableName);
+
+ return SgHostUtil.ensureSyncgroupHierarchies(remoteDb, tableNames)
+ // Syncgroup create is implicitly deferred via flatMap from a real observable.
+ // Create this syncgroup on the remote Syncbase to auto-join that remote and sync
+ // data to it. Otherwise, we won't actually write anything to the cloud syncbase.
+ .switchMap(db -> toObservable(db.getSyncgroup(sgName)
+ .create(mParams.getVContext(), spec, mParams.getMemberInfo()))
+ .doOnCompleted(() ->
+ log.info("Created syncgroup " + sgName + " remotely"))
+ .onErrorResumeNext(t -> t instanceof ExistException ?
+ Observable.just(null) : Observable.error(t)));
+ }
+
+ private Observable<?> joinExistingSyncgroup(final String sgName,
+ final SyncgroupSpec expectedSpec) {
+ return mParams.getDb().getObservable().switchMap(db -> {
+ final Syncgroup sg = db.getSyncgroup(sgName);
+
+ // These toObservables are implicitly deferred via switchMap from a real observable
+ return toObservable(sg.join(mParams.getVContext(), mParams.getMemberInfo()))
+ .doOnCompleted(() -> log.info("Joined syncgroup " + sgName))
+ .flatMap(spec -> spec.equals(expectedSpec) ? Observable.just(null) :
+ toObservable(sg.setSpec(mParams.getVContext(), expectedSpec, ""))
+ .doOnCompleted(() ->
+ log.info("Updated spec for syncgroup " + sgName)));
+ });
+ }
+
+ @Override
+ protected Observable<?> rxJoin(final String sgHost, final String sgName,
+ final SyncgroupSpec spec) {
+ // TODO(rosswang) try to join first
+ return Observable.concat(
+ ensureSyncgroup(sgHost, sgName, spec).ignoreElements(),
+ joinExistingSyncgroup(sgName, spec));
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserPeerSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserPeerSyncgroup.java
new file mode 100644
index 0000000..555ad51
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserPeerSyncgroup.java
@@ -0,0 +1,61 @@
+// 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.rx.syncbase;
+
+import io.v.baku.toolkit.BakuActivityTrait;
+import io.v.v23.services.syncbase.nosql.SyncgroupJoinFailedException;
+import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
+import io.v.v23.syncbase.nosql.Database;
+import io.v.v23.syncbase.nosql.Syncgroup;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import rx.Observable;
+
+import static net.javacrumbs.futureconverter.guavarx.FutureConverter.toObservable;
+
+/**
+ * This syncgroup strategy is a bit of a hack and its future is uncertain.
+ */
+@Accessors(prefix = "m")
+@Slf4j
+public class UserPeerSyncgroup extends UserSyncgroup {
+ public static UserPeerSyncgroup forActivity(final BakuActivityTrait t) {
+ return builder().activity(t).buildPeer();
+ }
+
+ public UserPeerSyncgroup(final Parameters params) {
+ super(params);
+ if (!(params.getDb().getRxApp().getRxSyncbase() instanceof RxAndroidSyncbase)) {
+ throw new IllegalArgumentException("UserPeerSyncgroup must be constructed with a " +
+ "local Syncbase server (RxAndroidSyncbase).");
+ }
+ }
+
+ private Observable<SyncgroupSpec> createOrJoinSyncgroup(final Database db, final String sgName,
+ final SyncgroupSpec spec) {
+ final Syncgroup sg = db.getSyncgroup(sgName);
+ return Observable.defer(() ->
+ toObservable(sg.join(mParams.getVContext(), mParams.getMemberInfo())))
+ .doOnCompleted(() -> log.info("Joined syncgroup " + sgName))
+ .onErrorResumeNext(t -> t instanceof SyncgroupJoinFailedException ?
+ toObservable(
+ sg.create(mParams.getVContext(), spec, mParams.getMemberInfo()))
+ .doOnCompleted(() -> log.info("Created syncgroup " + sgName))
+ .map(x -> spec) :
+ Observable.error(t));
+ }
+
+ @Override
+ protected Observable<?> rxJoin(final String sgHost, final String sgName,
+ final SyncgroupSpec spec) {
+ final RxAndroidSyncbase sb = (RxAndroidSyncbase) mParams.getDb().getRxApp().getRxSyncbase();
+ final Observable<Object> mount = SgHostUtil.ensureSyncgroupHost(
+ mParams.getVContext(), sb.getRxServer(), sgHost).share();
+
+ return mParams.getDb().getObservable()
+ .switchMap(db -> Observable.merge(mount.first().ignoreElements()
+ .concatWith(createOrJoinSyncgroup(db, sgName, spec)), mount));
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java
new file mode 100644
index 0000000..f942a40
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/UserSyncgroup.java
@@ -0,0 +1,268 @@
+// 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.rx.syncbase;
+
+import android.content.Context;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import io.v.baku.toolkit.BakuActivityTrait;
+import io.v.baku.toolkit.VAndroidContextTrait;
+import io.v.baku.toolkit.blessings.BlessingsUtils;
+import io.v.baku.toolkit.blessings.ClientUser;
+import io.v.v23.context.VContext;
+import io.v.v23.security.Blessings;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
+import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
+import io.v.v23.services.syncbase.nosql.TableRow;
+import java8.util.function.Function;
+import java8.util.function.Supplier;
+import java8.util.stream.Collectors;
+import lombok.Value;
+import lombok.experimental.Accessors;
+import rx.Observable;
+import rx.functions.Action2;
+
+// TODO(rosswang): Generalize this to other possible syncgroup strategies.
+@Accessors(prefix = "m")
+public abstract class UserSyncgroup extends RxSyncgroup {
+ public static final SgSuffixFormat<Parameters> DEFAULT_SYNCGROUP_SUFFIX =
+ SgSuffixFormats.discriminated("user");
+
+ public static class Builder {
+ protected VContext mVContext;
+ protected Observable<Blessings> mRxBlessings;
+ protected SyncHostLevel mSyncHostLevel;
+ protected SgSuffixFormat<? super Parameters> mSgSuffixFormat =
+ DEFAULT_SYNCGROUP_SUFFIX;
+ protected RxDb mDb;
+ protected Function<String, String> mDescriptionForUsername = u -> "User syncgroup for " + u;
+ protected Function<AccessList, Permissions> mPermissionsForAcl;
+ protected List<TableRow> mPrefixes = new ArrayList<>();
+ protected SyncgroupMemberInfo mMemberInfo = DEFAULT_SYNCGROUP_MEMBER_INFO;
+ protected Action2<Integer, Throwable> mOnError;
+
+ // helper for constructing a default UserAppSyncHost if needed
+ protected Context mAndroidContext;
+
+ public Builder vContext(final VContext vContext) {
+ mVContext = vContext;
+ return this;
+ }
+
+ public Builder rxBlessings(final Observable<Blessings> rxBlessings) {
+ mRxBlessings = rxBlessings;
+ return this;
+ }
+
+ public Builder syncHostLevel(final SyncHostLevel syncHostLevel) {
+ mSyncHostLevel = syncHostLevel;
+ mAndroidContext = null;
+ return this;
+ }
+
+ public Builder sgSuffixFormat(final SgSuffixFormat sgSuffixFormat) {
+ mSgSuffixFormat = sgSuffixFormat;
+ return this;
+ }
+
+ public Builder sgSuffix(final String sgSuffix) {
+ return sgSuffixFormat(SgSuffixFormats.simple(sgSuffix));
+ }
+
+ public Builder discriminatedSgSuffix(final String customSuffix) {
+ return sgSuffixFormat(SgSuffixFormats.discriminated(customSuffix));
+ }
+
+ public Builder db(final RxDb db) {
+ mDb = db;
+ return this;
+ }
+
+ public Builder descriptionForUsername(
+ final Function<String, String> descriptionForUsername) {
+ mDescriptionForUsername = descriptionForUsername;
+ return this;
+ }
+
+ public Builder permissionsForAcl(
+ final Function<AccessList, Permissions> permissionsForAcl) {
+ mPermissionsForAcl = permissionsForAcl;
+ return this;
+ }
+
+ /**
+ * This setter is not additive.
+ */
+ public Builder prefixes(final List<TableRow> prefixes) {
+ mPrefixes.clear();
+ mPrefixes.addAll(prefixes);
+ return this;
+ }
+
+ /**
+ * This setter is not additive.
+ */
+ public Builder prefixes(final TableRow... prefixes) {
+ mPrefixes.clear();
+ mPrefixes.addAll(Arrays.asList(prefixes));
+ return this;
+ }
+
+ /**
+ * This is an additive setter.
+ */
+ public Builder prefix(final TableRow prefix) {
+ mPrefixes.add(prefix);
+ return this;
+ }
+
+ /**
+ * This is an additive setter.
+ */
+ public Builder prefix(final String tableName, final String rowPrefix) {
+ return prefix(new TableRow(tableName, rowPrefix));
+ }
+
+ /**
+ * This is an additive setter.
+ */
+ public Builder prefix(final String tableName) {
+ return prefix(tableName, "");
+ }
+
+ public Builder memberInfo(final SyncgroupMemberInfo memberInfo) {
+ mMemberInfo = memberInfo;
+ return this;
+ }
+
+ public Builder onError(final Action2<Integer, Throwable> onError) {
+ mOnError = onError;
+ return this;
+ }
+
+ /**
+ * This is a composite setter for:
+ * <ul>
+ * <li>{@code vContext}</li>Context
+ * <li>{@code rxBlessings}</li>
+ * <li>{@code onError}</li>
+ * </ul>
+ * and should be called prior to any overrides for those fields.
+ */
+ public Builder activity(final VAndroidContextTrait<?> t) {
+ mAndroidContext = t.getAndroidContext();
+ return vContext(t.getVContext())
+ .rxBlessings(t.getBlessingsProvider().getRxBlessings())
+ .onError(t.getErrorReporter()::onError);
+ }
+
+ /**
+ * In addition to those fields in {@link #activity(VAndroidContextTrait)}, this
+ * additionally sets:
+ * <ul>
+ * <li>{@code db}</li>
+ * <li>and adds to {@code prefixes}</li>
+ * </ul>
+ */
+ public Builder activity(final BakuActivityTrait<?> t) {
+ return activity(t.getVAndroidContextTrait())
+ .db(t.getSyncbaseDb())
+ .prefix(t.getSyncbaseTableName());
+ }
+
+ protected Parameters buildParameters(
+ final Supplier<SyncHostLevel> defaultSyncHost,
+ final Function<AccessList, Permissions> defaultPermissionsForAcl) {
+ return new Parameters(mVContext, mRxBlessings,
+ mSyncHostLevel == null ? defaultSyncHost.get() : mSyncHostLevel,
+ mSgSuffixFormat, mDb, mDescriptionForUsername,
+ mPermissionsForAcl == null? defaultPermissionsForAcl : mPermissionsForAcl,
+ ImmutableList.copyOf(mPrefixes), mMemberInfo, mOnError);
+ }
+
+ public UserCloudSyncgroup buildCloud() {
+ return new UserCloudSyncgroup(buildParameters(
+ () -> ClientLevelCloudSync.DEFAULT,
+ acl -> BlessingsUtils.cloudSyngroupPermissions(acl,
+ UserCloudSyncgroup.DEBUG_SG_HOST_BLESSING)));
+ }
+
+ public UserPeerSyncgroup buildPeer() {
+ return new UserPeerSyncgroup(buildParameters(
+ () -> new UserAppSyncHost(mAndroidContext),
+ BlessingsUtils::syncgroupPermissions));
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Value
+ public static class Parameters {
+ VContext mVContext;
+ Observable<Blessings> mRxBlessings;
+ SyncHostLevel mSyncHostLevel;
+ SgSuffixFormat<? super Parameters> mSgSuffixFormat;
+ RxDb mDb;
+ Function<String, String> mDescriptionForUsername;
+ Function<AccessList, Permissions> mPermissionsForAcl;
+ ImmutableList<TableRow> mPrefixes;
+ SyncgroupMemberInfo mMemberInfo;
+ Action2<Integer, Throwable> mOnError;
+ }
+
+ protected final Parameters mParams;
+
+ public UserSyncgroup(final Parameters params) {
+ super(params.getOnError());
+ mParams = params;
+ }
+
+ private SyncgroupSpec createSpec(final ClientUser clientUser, final AccessList acl) {
+ return new SyncgroupSpec(
+ mParams.getDescriptionForUsername().apply(clientUser.getUsername()),
+ mParams.getPermissionsForAcl().apply(acl), mParams.getPrefixes(),
+ mParams.getSyncHostLevel().getRendezvousTableNames(clientUser), false);
+ }
+
+ protected abstract Observable<?> rxJoin(final String sgHost, final String sgName,
+ final SyncgroupSpec spec);
+
+ private Observable<?> rxJoin(final ClientUser clientUser, final AccessList acl) {
+ final String sgHost = mParams.getSyncHostLevel().getSyncgroupHostName(clientUser);
+ final String sgName = RxSyncbase.syncgroupName(sgHost,
+ mParams.getSgSuffixFormat().get(mParams));
+ final SyncgroupSpec spec = createSpec(clientUser, acl);
+
+ return rxJoin(sgHost, sgName, spec);
+ }
+
+ @Override
+ public Observable<?> rxJoin() {
+ return Observable.switchOnNext(mParams.getRxBlessings()
+ .map(b -> {
+ final AccessList acl = BlessingsUtils.blessingsToAcl(mParams.getVContext(), b);
+ final List<Observable<?>> joins =
+ BlessingsUtils.blessingsToClientUserStream(mParams.getVContext(), b)
+ .distinct()
+ .map(cu -> rxJoin(cu, acl))
+ .collect(Collectors.toList());
+ if (joins.isEmpty()) {
+ throw new NoSuchElementException("UserSyncgroup requires a username; no " +
+ "username blessings found. Blessings: " + b);
+ }
+ return Observable.merge(joins);
+ }));
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/overview.html b/baku-toolkit/lib/src/main/java/overview.html
new file mode 100644
index 0000000..0548c5f
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/overview.html
@@ -0,0 +1,14 @@
+<body>
+The Baku Android Toolkit includes software components facilitating the development of
+applications with distributed user interfaces. It is available from JCenter and Maven Central. The
+available versions are listed
+<a href="https://bintray.com/vanadium/io.v/baku-toolkit" target="_blank">here</a>. To use the Baku
+Toolkit from an Android Java project, ensure that the {@code build.gradle} has either
+{@code jcenter()} or {@code mavenCentral()} in its repositories, add
+{@code 'io.v:baku-toolkit:version'} as a {@code compile} dependency, and bind an
+<a href="http://www.slf4j.org/" target="_blank">SLF4J</a> logger as an APK dependency, like
+{@code apk ('org.slf4j:slf4j-android:1.7.12')}.
+
+<p>
+ For common usage, see the {@link io.v.baku.toolkit} package docs.
+</body>
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/RobolectricTestCase.java b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/RobolectricTestCase.java
deleted file mode 100644
index c3c3a72..0000000
--- a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/RobolectricTestCase.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.baku.toolkit;
-
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricGradleTestRunner;
-import org.robolectric.annotation.Config;
-
-import java.util.concurrent.TimeUnit;
-
-import rx.Observable;
-import rx.observables.BlockingObservable;
-
-@RunWith(RobolectricGradleTestRunner.class)
-@Config(constants = BuildConfig.class)
-public abstract class RobolectricTestCase {
- private static final long TIMEOUT_SECONDS = 5;
-
- public static <T> BlockingObservable<T> block(final Observable<T> source) {
- return source.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS).toBlocking();
- }
-
- public static <T> T first(final Observable<T> source) {
- return block(source).first();
- }
-}
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/bind/PrefixBindingBuilderTest.java b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/bind/PrefixBindingBuilderTest.java
new file mode 100644
index 0000000..15757d2
--- /dev/null
+++ b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/bind/PrefixBindingBuilderTest.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.baku.toolkit.bind;
+
+import android.widget.ListView;
+
+import org.junit.Test;
+
+public class PrefixBindingBuilderTest {
+ @Test(expected = IllegalStateException.class)
+ public void testMissingType() {
+ CollectionBinding.builder()
+ .onPrefix("foo")
+ .bindTo((ListView)null);
+ }
+}
diff --git a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragmentTest.java b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragmentTest.java
deleted file mode 100644
index 7991ff7..0000000
--- a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/AccountManagerBlessingsFragmentTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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.baku.toolkit.blessings;
-
-import android.content.Intent;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
-import org.powermock.modules.junit4.rule.PowerMockRule;
-import org.robolectric.util.FragmentTestUtil;
-
-import java.util.concurrent.TimeUnit;
-
-import io.v.android.v23.services.blessing.BlessingService;
-import io.v.baku.toolkit.RobolectricTestCase;
-import io.v.rx.RxTestCase;
-import io.v.v23.security.Blessings;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.util.async.Async;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-
-@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
-@SuppressStaticInitializationFor("io.v.baku.toolkit.blessings.BlessingsUtils")
-@PrepareForTest({Blessings.class, BlessingService.class, BlessingsUtils.class})
-public class AccountManagerBlessingsFragmentTest extends RobolectricTestCase {
- private static final int MOCK_RESULT_CODE = 42;
- private static final Intent
- BLESSING_REQUEST = mock(Intent.class),
- BLESSING_RESULT = mock(Intent.class);
-
- /**
- * We have to mock this fragment by extension rather than a Mockito spy because PowerMock won't
- * let Mockito properly mock methods inherited from android classes pulled in by Robolectric.
- */
- public static class MockFragment extends AccountManagerBlessingsFragment {
- @Override
- public void startActivityForResult(final Intent intent, final int requestCode) {
- assertEquals(BLESSING_REQUEST, intent);
- Async.start(() -> {
- onActivityResult(requestCode, MOCK_RESULT_CODE, BLESSING_RESULT);
- return null;
- }, AndroidSchedulers.mainThread());
- }
- }
-
- @Rule
- public final PowerMockRule rule = new PowerMockRule();
-
- @Test
- public void test() throws Exception {
- mockStatic(BlessingService.class);
- when(BlessingService.newBlessingIntent(any())).thenReturn(BLESSING_REQUEST);
-
- // Would ideally be private static final but we need PowerMockito to mock the final
- // Blessings class and since we're running with Robolectric, PowerMock is bound on the
- // instance rather than the class.
- final Blessings MOCK_BLESSINGS = PowerMockito.mock(Blessings.class);
-
- mockStatic(BlessingsUtils.class);
- when(BlessingsUtils.fromActivityResult(MOCK_RESULT_CODE, BLESSING_RESULT))
- .thenReturn(MOCK_BLESSINGS);
-
- final MockFragment fragment = new MockFragment();
-
- FragmentTestUtil.startVisibleFragment(fragment);
-
- assertEquals(MOCK_BLESSINGS, first(fragment.getRxBlessings()
- .timeout(RxTestCase.BLOCKING_DELAY_MS, TimeUnit.MILLISECONDS)));
- }
-}
diff --git a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeekerTest.java b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeekerTest.java
deleted file mode 100644
index 462afee..0000000
--- a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/ActivityBlessingsSeekerTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// 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.baku.toolkit.blessings;
-
-import android.app.Activity;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import io.v.v23.security.Blessings;
-import rx.functions.Action1;
-
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-
-@RunWith(PowerMockRunner.class)
-@SuppressStaticInitializationFor("io.v.baku.toolkit.blessings.BlessingsUtils")
-@PrepareForTest({Blessings.class, BlessingsUtils.class})
-public class ActivityBlessingsSeekerTest {
- private static class MockActivityBlessingsSeeker extends ActivityBlessingsSeeker {
- public MockActivityBlessingsSeeker(final Activity activity) {
- super(activity, null, false);
- }
-
- @Override
- protected void seekBlessings() {
- }
- }
-
- @Before
- public void setUp() {
- mockStatic(BlessingsUtils.class);
- }
-
- @Test
- public void testColdPassive() {
- // would NPE if it tries to do anytyhing
- new MockActivityBlessingsSeeker(null)
- .getPassiveRxBlessings()
- .subscribe(b -> fail("Unexpected blessings " + b));
- }
-
- @Test
- public void testBlessingsFromManager() throws Exception {
- final Activity activity = mock(Activity.class);
- @SuppressWarnings("unchecked")
- final Action1<Blessings>
- cold = mock(Action1.class),
- hot = mock(Action1.class);
- final Blessings
- b1 = PowerMockito.mock(Blessings.class),
- b2 = PowerMockito.mock(Blessings.class);
-
- PowerMockito.when(BlessingsUtils.readSharedPrefs(any())).thenReturn(b1, b2);
-
- final MockActivityBlessingsSeeker t = new MockActivityBlessingsSeeker(activity);
- t.getPassiveRxBlessings().subscribe(cold);
-
- t.getRxBlessings().subscribe(hot);
- verify(hot).call(b1);
- verify(cold).call(b1);
- verify(hot, never()).call(b2);
-
- t.refreshBlessings();
- verify(hot).call(b2);
- verify(cold).call(b2);
- }
-
- @Test
- public void testBlessingsFromProvider() throws Exception {
- final Activity activity = mock(Activity.class);
- @SuppressWarnings("unchecked")
- final Action1<Blessings> s = mock(Action1.class);
- final Blessings
- b1 = PowerMockito.mock(Blessings.class),
- b2 = PowerMockito.mock(Blessings.class);
-
- final MockActivityBlessingsSeeker t = new MockActivityBlessingsSeeker(activity);
- t.getRxBlessings().subscribe(s);
- verify(s, never()).call(any());
-
- t.setBlessings(b1);
- verify(s).call(b1);
-
- t.refreshBlessings();
- // The mock BlessingsManager will default to null, so it will seek blessings again.
- t.setBlessings(b2);
- verify(s).call(b2);
- }
-
- /**
- * Verifies that if a new subscriber needs to seek blessings, the new subscriber does not
- * receive blessings until the seek completes (does not receive old blessings), and that the old
- * subscriber is refreshed as well.
- */
- @Test
- public void testDeferOnNewSubscriber() throws Exception {
- final Activity activity = mock(Activity.class);
- @SuppressWarnings("unchecked")
- final Action1<Blessings>
- s1 = mock(Action1.class),
- s2 = mock(Action1.class);
- final Blessings
- b1 = PowerMockito.mock(Blessings.class),
- b2 = PowerMockito.mock(Blessings.class);
-
- final MockActivityBlessingsSeeker t = new MockActivityBlessingsSeeker(activity);
- t.getRxBlessings().subscribe(s1);
- t.setBlessings(b1);
-
- t.getRxBlessings().subscribe(s2);
- verify(s2, never()).call(any());
-
- t.setBlessings(b2);
- verify(s1).call(b2);
- verify(s2).call(b2);
- }
-}
diff --git a/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProviderTest.java b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProviderTest.java
new file mode 100644
index 0000000..c720a2a
--- /dev/null
+++ b/baku-toolkit/lib/src/test/java/io/v/baku/toolkit/blessings/BlessingsManagerBlessingsProviderTest.java
@@ -0,0 +1,133 @@
+// 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.baku.toolkit.blessings;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import io.v.android.libs.security.BlessingsManager;
+import io.v.v23.security.Blessings;
+import rx.functions.Action1;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@RunWith(PowerMockRunner.class)
+@SuppressStaticInitializationFor({"io.v.android.libs.security.BlessingsManager", "android.app.Fragment"})
+@PrepareForTest({Blessings.class, BlessingsManager.class})
+public class BlessingsManagerBlessingsProviderTest {
+ @Before
+ public void setUp() {
+ mockStatic(BlessingsManager.class);
+ }
+
+ @Test
+ public void testColdPassive() {
+ new BlessingsManagerBlessingsProvider(null, null)
+ .getPassiveRxBlessings()
+ .subscribe(b -> fail("Unexpected blessings " + b));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testBlessingsFromManager() throws Exception {
+ final Action1<Blessings>
+ cold = mock(Action1.class),
+ hot = mock(Action1.class);
+ final Blessings
+ b1 = PowerMockito.mock(Blessings.class),
+ b2 = PowerMockito.mock(Blessings.class);
+
+ when(BlessingsManager.getBlessings(any(), any(), any(), anyBoolean()))
+ .thenReturn(Futures.immediateFuture(b1), Futures.immediateFuture(b2));
+
+ final RefreshableBlessingsProvider t = new BlessingsManagerBlessingsProvider(null, null);
+ t.getPassiveRxBlessings().subscribe(cold);
+
+ t.getRxBlessings().subscribe(hot);
+ verify(hot).call(b1);
+ verify(cold).call(b1);
+ verify(hot, never()).call(b2);
+
+ t.refreshBlessings();
+ verify(hot).call(b2);
+ verify(cold).call(b2);
+ }
+
+ /**
+ * Verifies that if a new subscriber needs to seek blessings, the new subscriber does not
+ * receive blessings until the seek completes (does not receive old blessings), and that the old
+ * subscriber is refreshed as well.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testDeferOnNewSubscriber() throws Exception {
+ final Action1<Blessings>
+ s1 = mock(Action1.class),
+ s2 = mock(Action1.class);
+ final Blessings
+ b1 = PowerMockito.mock(Blessings.class),
+ b2 = PowerMockito.mock(Blessings.class);
+
+ final SettableFuture<Blessings>
+ bf1 = SettableFuture.create(),
+ bf2 = SettableFuture.create();
+
+ when(BlessingsManager.getBlessings(any(), any(), any(), anyBoolean()))
+ .thenReturn(bf1, bf2);
+
+ final RefreshableBlessingsProvider t = new BlessingsManagerBlessingsProvider(null, null);
+ t.getRxBlessings().subscribe(s1);
+ bf1.set(b1);
+
+ t.getRxBlessings().subscribe(s2);
+ verify(s2, never()).call(any());
+
+ bf2.set(b2);
+ verify(s1).call(b2);
+ verify(s2).call(b2);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testConcurrentSeeks() {
+ final Action1<Blessings>
+ s1 = mock(Action1.class),
+ s2 = mock(Action1.class);
+ final Blessings b = PowerMockito.mock(Blessings.class);
+
+ final SettableFuture<Blessings>
+ bf = SettableFuture.create();
+
+ when(BlessingsManager.getBlessings(any(), any(), any(), anyBoolean()))
+ .thenReturn(bf, Futures.immediateFailedFuture(
+ new AssertionFailedError("Expected at most one getBlessings call.")));
+
+ final RefreshableBlessingsProvider t = new BlessingsManagerBlessingsProvider(null, null);
+ t.getRxBlessings().subscribe(s1);
+ t.getRxBlessings().subscribe(s2);
+
+ bf.set(b);
+
+ verify(s1).call(b);
+ verify(s2).call(b);
+ }
+}
diff --git a/baku-toolkit/lib/src/test/java/io/v/rx/RxInputChannelTest.java b/baku-toolkit/lib/src/test/java/io/v/rx/RxInputChannelTest.java
index ef016a2..dd7e6d6 100644
--- a/baku-toolkit/lib/src/test/java/io/v/rx/RxInputChannelTest.java
+++ b/baku-toolkit/lib/src/test/java/io/v/rx/RxInputChannelTest.java
@@ -13,7 +13,6 @@
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
-import io.v.baku.toolkit.RobolectricTestCase;
import io.v.v23.InputChannel;
import io.v.v23.verror.EndOfFileException;
@@ -32,7 +31,7 @@
Futures.immediateFuture(1),
Futures.immediateFuture(2),
Futures.immediateFailedFuture(PowerMockito.mock(EndOfFileException.class)));
- assertEquals(ImmutableList.of(1, 2), RobolectricTestCase.first(
+ assertEquals(ImmutableList.of(1, 2), RxTestCase.first(
RxInputChannel.wrap(mockInputChannel).autoConnect().toList()));
}
}
diff --git a/baku-toolkit/lib/src/test/java/io/v/rx/RxTestCase.java b/baku-toolkit/lib/src/test/java/io/v/rx/RxTestCase.java
index d5b33e2..0562a60 100644
--- a/baku-toolkit/lib/src/test/java/io/v/rx/RxTestCase.java
+++ b/baku-toolkit/lib/src/test/java/io/v/rx/RxTestCase.java
@@ -13,9 +13,12 @@
import org.junit.After;
import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
import java8.util.stream.Collectors;
import java8.util.stream.StreamSupport;
+import rx.Observable;
+import rx.observables.BlockingObservable;
import static org.junit.Assert.fail;
@@ -28,6 +31,14 @@
return 2 * nominal.getMillis();
}
+ public static <T> BlockingObservable<T> block(final Observable<T> source) {
+ return source.timeout(BLOCKING_DELAY_MS, TimeUnit.SECONDS).toBlocking();
+ }
+
+ public static <T> T first(final Observable<T> source) {
+ return block(source).first();
+ }
+
private final Multimap<Class<? extends Throwable>, Throwable> mErrors =
Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
diff --git a/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/RxTableTest.java b/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/RxTableTest.java
index 8d70acb..fc6f3da 100644
--- a/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/RxTableTest.java
+++ b/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/RxTableTest.java
@@ -21,7 +21,6 @@
import io.v.rx.RxTestCase;
import io.v.rx.SubscriberInputChannel;
-import io.v.v23.context.CancelableVContext;
import io.v.v23.context.VContext;
import io.v.v23.services.syncbase.nosql.KeyValue;
import io.v.v23.services.watch.ResumeMarker;
@@ -77,7 +76,6 @@
mChanges.subscribe(watchChan);
final VContext ctx = mock(VContext.class);
- final CancelableVContext cctx = mock(CancelableVContext.class);
final RxDb rxdb = mock(RxDb.class);
final Database db = mock(Database.class);
final BatchDatabase bdb = mock(BatchDatabase.class);
@@ -85,7 +83,7 @@
mockStatic(VomUtil.class);
- when(ctx.withCancel()).thenReturn(cctx);
+ when(ctx.withCancel()).thenReturn(ctx);
when(rxdb.getVContext()).thenReturn(ctx);
when(rxdb.getObservable()).thenReturn(Observable.just(db));
when(db.getTable("t")).thenReturn(t);
diff --git a/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/GlobalUserSyncgroupTest.java b/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/UserPeerSyncgroupTest.java
similarity index 91%
rename from baku-toolkit/lib/src/test/java/io/v/rx/syncbase/GlobalUserSyncgroupTest.java
rename to baku-toolkit/lib/src/test/java/io/v/rx/syncbase/UserPeerSyncgroupTest.java
index 1102bb0..6e8da27 100644
--- a/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/GlobalUserSyncgroupTest.java
+++ b/baku-toolkit/lib/src/test/java/io/v/rx/syncbase/UserPeerSyncgroupTest.java
@@ -21,7 +21,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import io.v.baku.toolkit.blessings.BlessingsUtils;
-import io.v.debug.SyncbaseClient;
+import io.v.baku.toolkit.blessings.ClientUser;
+import io.v.debug.SyncbaseAndroidClient;
import io.v.rx.RxMountState;
import io.v.rx.RxTestCase;
import io.v.v23.context.VContext;
@@ -50,7 +51,7 @@
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("io.v.baku.toolkit.blessings.BlessingsUtils")
@PrepareForTest({BlessingsUtils.class, SgHostUtil.class})
-public class GlobalUserSyncgroupTest extends RxTestCase {
+public class UserPeerSyncgroupTest extends RxTestCase {
private static final long STATUS_POLLING_DELAY_MS = verificationDelay(
RxMountState.DEFAULT_POLLING_INTERVAL);
@@ -65,11 +66,11 @@
private final Database mDb = mock(Database.class);
private final Syncgroup mSg = mock(Syncgroup.class);
- private RxSyncbase mSb;
+ private RxAndroidSyncbase mSb;
@Before
public void setUp() throws Exception {
- final SyncbaseClient sbClient = mock(SyncbaseClient.class);
+ final SyncbaseAndroidClient sbClient = mock(SyncbaseAndroidClient.class);
final SyncbaseApp app = mock(SyncbaseApp.class);
when(sbClient.getRxServer()).thenReturn(mRxServer);
@@ -86,7 +87,7 @@
when(mSg.join(any(), any())).thenReturn(Futures.immediateFuture(null));
- mSb = new RxSyncbase(null, sbClient);
+ mSb = new RxAndroidSyncbase(null, sbClient);
PowerMockito.spy(SgHostUtil.class);
}
@@ -96,20 +97,19 @@
.thenReturn(mSg);
final Stopwatch t = Stopwatch.createStarted();
- final Subscription subscription = GlobalUserSyncgroup.builder()
+ final Subscription subscription = UserPeerSyncgroup.builder()
.vContext(mVContext)
.rxBlessings(mRxBlessings)
.syncHostLevel(new UserAppSyncHost("app", "sghost", "sgmt"))
.sgSuffix("sg")
- .syncbase(mSb)
.db(mSb.rxApp("app").rxDb("db"))
.onError((m, e) -> catchAsync(e))
- .build()
+ .buildPeer()
.join();
PowerMockito.spy(BlessingsUtils.class);
- PowerMockito.doReturn(RefStreams.of("foo@bar.com"))
- .when(BlessingsUtils.class, "blessingsToUsernameStream", any(), any());
+ PowerMockito.doReturn(RefStreams.of(new ClientUser("fooclient", "foo@bar.com")))
+ .when(BlessingsUtils.class, "blessingsToClientUserStream", any(), any());
PowerMockito.doReturn(null)
.when(BlessingsUtils.class, "blessingsToAcl", any(), any());
PowerMockito.doReturn(null)
@@ -117,7 +117,7 @@
long elapsed = t.elapsed(TimeUnit.MILLISECONDS);
if (elapsed > BLOCKING_DELAY_MS) {
- fail("GlobalUserSyncgroup.join should not block; took " + elapsed + " ms (threshold " +
+ fail("UserPeerSyncgroup.join should not block; took " + elapsed + " ms (threshold " +
BLOCKING_DELAY_MS + " ms)");
}
return subscription;