Merge "remove references to maven.v.io"
diff --git a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
index 95309c2..388413f 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/PdfViewerActivity.java
@@ -14,6 +14,7 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
+import android.widget.Toast;
 
 import com.google.common.io.ByteStreams;
 
@@ -173,14 +174,14 @@
 
     private void createAndJoinDeviceSet(Uri fileUri) {
         // Get the file content.
-        java.io.File jFile = getFileFromUri(fileUri);
-        if (jFile == null) {
-            Log.e(TAG, "Could not get the file content of Uri: " + fileUri.toString());
-            return;
-        }
+        byte[] bytes = getBytesFromUri(fileUri);
 
         // Create a vdl File object representing this pdf file and put it in the db.
-        File vFile = createVdlFile(jFile, fileUri);
+        File vFile = mDB.storeBytes(bytes, getTitleFromUri(fileUri));
+        Log.i(TAG, "vFile created: " + vFile);
+        if (vFile == null) {
+            Log.e(TAG, "Could not store the file content of Uri: " + fileUri.toString());
+        }
         mDB.addFile(vFile);
 
         // Create a device set object and put it in the db.
@@ -202,15 +203,6 @@
         leaveDeviceSet();
     }
 
-    private File createVdlFile(java.io.File jFile, Uri uri) {
-        String id = jFile.getName();
-        String title = getTitleFromUri(uri);
-        long size = jFile.length();
-        String type = Constants.PDF_MIME_TYPE;
-
-        return new File(id, null, title, size, type);
-    }
-
     private DeviceMeta createDeviceMeta() {
         String deviceId = DeviceInfoFactory.getDeviceId(this);
         int page = 1;
@@ -229,8 +221,23 @@
     }
 
     private void joinDeviceSet(DeviceSet ds) {
-        // TODO(youngseokyoon): use the blobref instead.
+        // Get the file contents from the database
+        // TODO(youngseokyoon): get the blob asynchronously. right now, it's blocking the UI thread.
+        File file = mDB.getFileList().getItemById(ds.getFileId());
+        byte[] bytes = mDB.readBytes(file);
+        if (bytes == null) {
+            Toast.makeText(this, "Could not load the file contents.", Toast.LENGTH_LONG).show();
+            return;
+        }
+
+        // The pdf viewer widget requires the file to be an actual java.io.File object.
+        // Create a temporary file and write the contents.
         java.io.File jFile = new java.io.File(getCacheDir(), ds.getFileId());
+        try (FileOutputStream out = new FileOutputStream(jFile)) {
+            out.write(bytes);
+        } catch (IOException e) {
+            handleException(e);
+        }
 
         // Initialize the pdf viewer widget with the file content.
         // TODO(youngseokyoon): enable swipe and handle the page change events.
@@ -266,37 +273,12 @@
         mCurrentDS = null;
     }
 
-    private java.io.File getFileFromUri(Uri uri) {
-        Log.i(TAG, "File Uri: " + uri.toString());
+    private byte[] getBytesFromUri(Uri uri) {
+        Log.i(TAG, "getBytesFromUri: " + uri.toString());
 
         try (InputStream in = getContentResolver().openInputStream(uri)) {
             // Get the entire file contents as a byte array.
-            byte[] bytes = ByteStreams.toByteArray(in);
-
-            // Write the contents in a temporary file.
-            // For now, use the md5 hash string of the file as the filename.
-            // TODO(youngseokyoon): use the Syncbase blob to store the file.
-
-            String fileKey = IdFactory.getFileId(bytes);
-            if (fileKey == null) {
-                fileKey = IdFactory.getRandomId();
-                Log.w(TAG, "Could not get the MD5 hash string for Uri: " + uri.toString());
-                Log.w(TAG, "- Using a random UUID instead.");
-            }
-            Log.i(TAG, "FileKey: " + fileKey);
-
-            java.io.File jFile = new java.io.File(getCacheDir(), fileKey);
-            if (jFile.exists() && jFile.length() == bytes.length) {
-                Log.i(TAG, "The file already exists in the cache directory.");
-                return jFile;
-            }
-
-            Log.i(TAG, "Creating pdf file: " + jFile.getPath());
-            try (FileOutputStream out = new FileOutputStream(jFile)) {
-                out.write(bytes);
-            }
-
-            return jFile;
+            return ByteStreams.toByteArray(in);
         } catch (IOException e) {
             handleException(e);
         }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
index 703f0bc..582c304 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
@@ -152,4 +152,24 @@
      */
     void deleteDeviceSet(String id);
 
+    /**
+     * Stores the given bytes and returns a File object representing the written data, which can be
+     * passed to readBytes() method to read the data back.
+     *
+     * The returned File object should be explicitly added to the database by calling addFile().
+     *
+     * @param bytes bytes to be written.
+     * @param title title of this file.
+     * @return      a File object representing the written data.
+     */
+    File storeBytes(byte[] bytes, String title);
+
+    /**
+     * Reads the bytes from the File object.
+     *
+     * @param file the file to be read.
+     * @return     the file contents as a byte array.
+     */
+    byte[] readBytes(File file);
+
 }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
index e9a9443..43c83b5 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
@@ -7,11 +7,19 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 
+import com.google.common.io.ByteStreams;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import io.v.android.apps.reader.Constants;
 import io.v.android.apps.reader.model.DeviceInfoFactory;
+import io.v.android.apps.reader.model.IdFactory;
 import io.v.android.apps.reader.model.Listener;
 import io.v.android.apps.reader.vdl.Device;
 import io.v.android.apps.reader.vdl.DeviceSet;
@@ -22,11 +30,15 @@
  */
 public class FakeDB implements DB {
 
+    private static final String TAG = FakeDB.class.getSimpleName();
+
+    private Context mContext;
     private FakeFileList mFileList;
     private FakeDeviceList mDeviceList;
     private FakeDeviceSetList mDeviceSetList;
 
     public FakeDB(Context context) {
+        mContext = context;
         mFileList = new FakeFileList();
         mDeviceList = new FakeDeviceList();
         mDeviceSetList = new FakeDeviceSetList();
@@ -187,4 +199,37 @@
     public void deleteDeviceSet(String id) {
         mDeviceSetList.removeItemById(id);
     }
+
+    @Override
+    public File storeBytes(byte[] bytes, String title) {
+        // In Fake DB, store the bytes as a temporary file in the local filesystem.
+        String id = IdFactory.getFileId(bytes);
+
+        java.io.File jFile = new java.io.File(mContext.getCacheDir(), id);
+        try (FileOutputStream out = new FileOutputStream(jFile)) {
+            out.write(bytes);
+        } catch (IOException e) {
+            Log.e(TAG, e.getMessage(), e);
+        }
+
+        return new File(
+                id,
+                null,
+                title,
+                bytes.length,
+                Constants.PDF_MIME_TYPE
+        );
+    }
+
+    @Override
+    public byte[] readBytes(File file) {
+        java.io.File jFile = new java.io.File(mContext.getCacheDir(), file.getId());
+        try (FileInputStream in = new FileInputStream(jFile)) {
+            return ByteStreams.toByteArray(in);
+        } catch (IOException e) {
+            Log.e(TAG, e.getMessage(), e);
+        }
+
+        return null;
+    }
 }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
index 0dd0e2a..38628a6 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
@@ -15,12 +15,16 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
 import io.v.android.apps.reader.model.DeviceInfoFactory;
+import io.v.android.apps.reader.model.IdFactory;
 import io.v.android.apps.reader.model.Listener;
 import io.v.android.apps.reader.vdl.Device;
 import io.v.android.apps.reader.vdl.DeviceSet;
@@ -44,6 +48,7 @@
 import io.v.v23.security.access.Constants;
 import io.v.v23.security.access.Permissions;
 import io.v.v23.services.syncbase.nosql.BatchOptions;
+import io.v.v23.services.syncbase.nosql.BlobRef;
 import io.v.v23.services.syncbase.nosql.KeyValue;
 import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
 import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
@@ -53,6 +58,8 @@
 import io.v.v23.syncbase.SyncbaseApp;
 import io.v.v23.syncbase.SyncbaseService;
 import io.v.v23.syncbase.nosql.BatchDatabase;
+import io.v.v23.syncbase.nosql.BlobReader;
+import io.v.v23.syncbase.nosql.BlobWriter;
 import io.v.v23.syncbase.nosql.Database;
 import io.v.v23.syncbase.nosql.RowRange;
 import io.v.v23.syncbase.nosql.Syncgroup;
@@ -486,6 +493,51 @@
         }
     }
 
+    @Override
+    public File storeBytes(byte[] bytes, String title) {
+        // In case of Syncbase DB, store the bytes as a blob.
+        // TODO(youngseokyoon): check if the same blob is already in the database.
+        try {
+            BlobWriter writer = mLocalSB.db.writeBlob(mVContext, null);
+            OutputStream out = writer.stream(mVContext);
+            out.write(bytes);
+            out.close();
+
+            writer.commit(mVContext);
+
+            BlobRef ref = writer.getRef();
+
+            return new File(
+                    IdFactory.getFileId(bytes),
+                    ref,
+                    title,
+                    bytes.length,
+                    io.v.android.apps.reader.Constants.PDF_MIME_TYPE
+            );
+        } catch (VException | IOException e) {
+            handleError("Could not write the blob: " + e.getMessage());
+        }
+
+        return null;
+    }
+
+    @Override
+    public byte[] readBytes(File file) {
+        if (file == null || file.getRef() == null) {
+            return null;
+        }
+
+        try {
+            BlobReader reader = mLocalSB.db.readBlob(mVContext, file.getRef());
+            return ByteStreams.toByteArray(reader.stream(mVContext, 0L));
+        } catch (VException | IOException e) {
+            handleError("Could not read the blob " + file.getRef().toString()
+                    + ": " + e.getMessage());
+        }
+
+        return null;
+    }
+
     private void handleError(String msg) {
         Log.e(TAG, msg);
         Toast.makeText(mContext, msg, Toast.LENGTH_LONG).show();
diff --git a/web/Makefile b/web/Makefile
index e32d7f8..5543259 100644
--- a/web/Makefile
+++ b/web/Makefile
@@ -105,7 +105,7 @@
 		--root-dir="tmp/syncbase_$(id)" \
 		--name="users/$(email)/reader/$(id)/syncbase" \
 		--v23.namespace.root="/ns.dev.v.io:8101" \
-		--v23.proxy="/ns.dev.v.io:8101/proxy" \
+		--v23.proxy="proxy" \
 		--v23.tcp.address=":$(syncbase_port)" \
 		--v23.credentials="credentials" \
 		--v23.permissions.literal='{"Admin":{"In":["..."]},"Write":{"In":["..."]},"Read":{"In":["..."]},"Resolve":{"In":["..."]},"Debug":{"In":["..."]}}'
@@ -120,7 +120,7 @@
 		--root-dir="tmp/cloudsync" \
 		--name="users/$(email)/reader/cloudsync" \
 		--v23.namespace.root="/ns.dev.v.io:8101" \
-		--v23.proxy="/ns.dev.v.io:8101/proxy" \
+		--v23.proxy="proxy" \
 		--v23.tcp.address=":$(cloudsync_port)" \
 		--v23.credentials="credentials" \
 		--v23.permissions.literal='{"Admin":{"In":["..."]},"Write":{"In":["..."]},"Read":{"In":["..."]},"Resolve":{"In":["..."]},"Debug":{"In":["..."]}}'