Skip to content

Commit

Permalink
Fix blob query results (#61)
Browse files Browse the repository at this point in the history
* updating dart fixes

* updating platform code

* added digest to the JSON for blobs

* Changed the way lazy loading works slightly to make it easier to support.

* Bug fix.

* Fix issues with the map not being preserved during saves.

* Error with encoding the Blobs on ios

* Error with encoding the Blobs on ios

* Added blob caching

* Updated formatting

* Added Fragments to expose the
  • Loading branch information
bawelter committed Aug 7, 2020
1 parent 455a918 commit 7f6106e
Show file tree
Hide file tree
Showing 41 changed files with 815 additions and 532 deletions.
195 changes: 138 additions & 57 deletions android/src/main/java/com/saltechsystems/couchbase_lite/CBManager.java
@@ -1,35 +1,37 @@
package com.saltechsystems.couchbase_lite;

import android.content.res.AssetManager;
import android.os.Debug;

import com.couchbase.lite.Array;
import com.couchbase.lite.Blob;
import com.couchbase.lite.ConcurrencyControl;
import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.Database;
import com.couchbase.lite.DatabaseConfiguration;
import com.couchbase.lite.Dictionary;
import com.couchbase.lite.Document;
import com.couchbase.lite.IndexBuilder;
import com.couchbase.lite.ListenerToken;
import com.couchbase.lite.LogFileConfiguration;
import com.couchbase.lite.LogLevel;
import com.couchbase.lite.MutableDocument;
import com.couchbase.lite.Replicator;
import com.couchbase.lite.Query;
import com.couchbase.lite.ValueIndexItem;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.Log;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class CBManager {
private HashMap<String, Database> mDatabase = new HashMap<>();
private HashMap<String, Query> mQueries = new HashMap<>();
private final static HashMap<String, Blob> mBlobs = new HashMap<>();
private HashMap<String, ListenerToken> mQueryListenerTokens = new HashMap<>();
private HashMap<String, Replicator> mReplicators = new HashMap<>();
private HashMap<String, ListenerToken[]> mReplicatorListenerTokens = new HashMap<>();
Expand All @@ -52,29 +54,29 @@ Database getDatabase(String name) {
}

Map<String, Object> saveDocument(Database database, Map<String, Object> _map, ConcurrencyControl concurrencyControl) throws CouchbaseLiteException {
MutableDocument mutableDoc = new MutableDocument(getParsedMap(_map, null));
MutableDocument mutableDoc = new MutableDocument(convertSETDictionary(_map));
boolean success = database.save(mutableDoc, concurrencyControl);
HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("success", success);
if (success) {
resultMap.put("id", mutableDoc.getId());
resultMap.put("sequence", mutableDoc.getSequence());
resultMap.put("doc", getJSONMap(mutableDoc.toMap()));
resultMap.put("doc", _documentToMap(mutableDoc));
}
return resultMap;
}

Map<String, Object> saveDocumentWithId(Database database, String _id, Map<String, Object> _map, ConcurrencyControl concurrencyControl) throws CouchbaseLiteException {
HashMap<String, Object> resultMap = new HashMap<>();
MutableDocument mutableDoc = new MutableDocument(_id, getParsedMap(_map, null));
MutableDocument mutableDoc = new MutableDocument(_id, convertSETDictionary(_map));

boolean success = database.save(mutableDoc, concurrencyControl);

resultMap.put("success", success);
if (success) {
resultMap.put("id", mutableDoc.getId());
resultMap.put("sequence", mutableDoc.getSequence());
resultMap.put("doc", getJSONMap(mutableDoc.toMap()));
resultMap.put("doc", _documentToMap(mutableDoc));
}

return resultMap;
Expand All @@ -96,54 +98,142 @@ Map<String, Object> saveDocumentWithId(Database database, String _id, long seque
mutableDoc = document.toMutable();
}

mutableDoc.setData(getParsedMap(_map, document.toMap()));
mutableDoc.setData(convertSETDictionary(_map));

boolean success = database.save(mutableDoc, concurrencyControl);

resultMap.put("success", success);
if (success) {
resultMap.put("id", mutableDoc.getId());
resultMap.put("sequence", mutableDoc.getSequence());
resultMap.put("doc", getJSONMap(mutableDoc.toMap()));
resultMap.put("doc", _documentToMap(mutableDoc));
}
return resultMap;
}

private Map<String, Object> getParsedMap(Map<String, Object> _map, Map<String, Object> doc) {
static Blob getBlobWithDigest(String digest) {
synchronized (mBlobs) {
return mBlobs.get(digest);
}
}

static void setBlobWithDigest(String digest, Blob blob) {
synchronized (mBlobs) {
mBlobs.put(digest, blob);
}
}

static void clearBlobCache() {
synchronized (mBlobs) {
mBlobs.clear();
}
}

private Map<String, Object> _documentToMap(Document doc) {
HashMap<String,Object> parsed = new HashMap<>();
for (Map.Entry<String,Object> entry: _map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map<?, ?>) {
Map<String, Object> parsedMap;
Object originValue = doc == null ? null : doc.get(entry.getKey());
for (String key: doc.getKeys()) {
parsed.put(key, convertGETValue(doc.getValue(key)));
}

if (originValue instanceof Map<?,?>) {
parsedMap = getParsedMap(getMapFromGenericMap(value), getMapFromGenericMap(originValue));
} else {
parsedMap = getParsedMap(getMapFromGenericMap(value), null);
}
return parsed;
}

if (parsedMap.get("@type") instanceof String && ((String) parsedMap.get("@type")).equals("blob")) {
if (parsedMap.get("data") instanceof byte[] && parsedMap.get("contentType") instanceof String) {
String contentType = (String) parsedMap.get("contentType");
byte[] content = (byte[]) parsedMap.get("data");
parsed.put(entry.getKey(), new Blob(contentType,content));
} else if (originValue instanceof Blob) {
// Prevent blob from being deleted since the data isn't passed
parsed.put(entry.getKey(), originValue);
}
static Object convertSETValue(Object value) {
if (value instanceof Map<?, ?>) {
Map<String, Object> result = convertSETDictionary(getMapFromGenericMap(value));

if (result.get("@type") instanceof String && result.get("@type").equals("blob")) {
if (result.get("digest") instanceof String) {
// Prevent blob from updating when it doesn't change
return getBlobWithDigest((String) result.get("digest"));
} else if (result.get("data") instanceof byte[] && result.get("content_type") instanceof String) {
String contentType = (String) result.get("content_type");
byte[] content = (byte[]) result.get("data");
return new Blob(contentType,content);
} else {
parsed.put(entry.getKey(), parsedMap);
// Preserve the map value
return result;
}
} else {
parsed.put(entry.getKey(),value);
return result;
}
} else if (value instanceof List<?>) {
return convertSETArray(getListFromGenericList(value));
} else {
return value;
}
}

return parsed;
static Map<String, Object> convertSETDictionary(Map<String, Object> _map) {
if (_map == null) {
return null;
}

HashMap<String,Object> result = new HashMap<>();
for (Map.Entry<String,Object> entry: _map.entrySet()) {
result.put(entry.getKey(), convertSETValue(entry.getValue()));
}

return result;
}

private Map<String, Object> getMapFromGenericMap(Object objectMap) {
static List<Object> convertSETArray(List<Object> array) {
if (array == null) {
return null;
}

List<Object> rtnList = new ArrayList<>();
for (Object value: array) {
rtnList.add(convertSETValue(value));
}

return rtnList;
}

static Map<String,Object> convertGETDictionary(Dictionary dict) {
HashMap<String, Object> rtnMap = new HashMap<>();
for (String key: dict.getKeys()) {
rtnMap.put(key, convertGETValue(dict.getValue(key)));
}

return rtnMap;
}

static List<Object> convertGETArray(Array array) {
List<Object> rtnList = new ArrayList<>();
for (int idx = 0; idx < array.count(); idx++) {
rtnList.add(convertGETValue(array.getValue(idx)));
}

return rtnList;
}

static Object convertGETValue(Object value) {
if (value instanceof Blob) {
Blob blob = (Blob) value;
String digest = blob.digest();
if (digest != null) {
// Store the blob for retrieving the content
setBlobWithDigest(digest,blob);
}

// Don't return the data, JSONMessageCodec doesn't support it
HashMap<String,Object> json = new HashMap<>();
json.put("content_type", blob.getContentType());
json.put("digest", digest);
json.put("length", blob.length());
json.put("@type","blob");
return json;
} else if (value instanceof Dictionary){
return convertGETDictionary((Dictionary) value);
} else if (value instanceof Array){
return convertGETArray((Array) value);
} else {
return value;
}
}

private static Map<String, Object> getMapFromGenericMap(Object objectMap) {
Map<String, Object> resultMap = new HashMap<>();
if (objectMap instanceof Map<?, ?>) {
Map<?,?> genericMap = (Map<?,?>) objectMap;
Expand All @@ -154,12 +244,23 @@ private Map<String, Object> getMapFromGenericMap(Object objectMap) {
return resultMap;
}

private static List<Object> getListFromGenericList(Object objectList) {
List<Object> resultList = new ArrayList<>();
if (objectList instanceof List<?>) {
List<?> genericList = (List<?>) objectList;
for (Object value: genericList) {
resultList.add(value);
}
}
return resultList;
}

Map<String, Object> getDocumentWithId(Database database, String _id) {
HashMap<String, Object> resultMap = new HashMap<>();

Document document = database.getDocument(_id);
if (document != null) {
resultMap.put("doc", getJSONMap(document.toMap()));
resultMap.put("doc", _documentToMap(document));
resultMap.put("id", document.getId());
resultMap.put("sequence", document.getSequence());
} else {
Expand All @@ -170,26 +271,6 @@ Map<String, Object> getDocumentWithId(Database database, String _id) {
return resultMap;
}

private Map<String, Object> getJSONMap(Map<String, Object> _map) {
HashMap<String,Object> parsed = new HashMap<>();
for (Map.Entry<String,Object> entry: _map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Blob) {
Blob blob = (Blob) value;
HashMap<String,Object> json = new HashMap<>();
json.put("contentType", blob.getContentType());
json.put("digest", blob.digest());
json.put("length", blob.length());
json.put("@type","blob");
parsed.put(entry.getKey(),json);
} else {
parsed.put(entry.getKey(),entry.getValue());
}
}

return parsed;
}

void deleteDocumentWithId(Database database, String _id) throws CouchbaseLiteException {
HashMap<String, Object> resultMap = new HashMap<>();

Expand Down
Expand Up @@ -5,9 +5,7 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.couchbase.lite.Array;
import com.couchbase.lite.Blob;
import com.couchbase.lite.BuildConfig;
import com.couchbase.lite.ConcurrencyControl;
Expand All @@ -16,7 +14,6 @@
import com.couchbase.lite.Database;
import com.couchbase.lite.DatabaseChange;
import com.couchbase.lite.DatabaseChangeListener;
import com.couchbase.lite.Document;
import com.couchbase.lite.DocumentFlag;
import com.couchbase.lite.DocumentReplication;
import com.couchbase.lite.DocumentReplicationListener;
Expand All @@ -31,15 +28,13 @@
import com.couchbase.lite.Replicator;
import com.couchbase.lite.ReplicatorChange;
import com.couchbase.lite.ReplicatorChangeListener;
import com.couchbase.lite.ResultSet;
import com.couchbase.lite.ValueIndex;
import com.couchbase.lite.ValueIndexItem;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -146,6 +141,32 @@ private ValueIndex inflateValueIndex(List<Map<String, Object>> items) {
private class DatabaseCallHander implements MethodCallHandler {
@Override
public void onMethodCall(MethodCall call, final Result result) {
switch (call.method) {
case ("getBlobContentWithDigest"):
if (!call.hasArgument("digest")) {
result.error("errArgs", "Database Error: Invalid Arguments", call.arguments.toString());
return;
}

String _digest = call.argument("digest");

// Don't load the content if it isn't found
Blob _blob = CBManager.getBlobWithDigest(_digest);
if (_blob != null) {
result.success(_blob.getContent());
} else {
result.success(null);
}
return;
case ("clearBlobCache"):
CBManager.clearBlobCache();
result.success(null);
return;
default:
break;
}

// All other methods are database dependent
if (!call.hasArgument("database")) {
result.error("errArgs", "Error: Missing database", call.arguments.toString());
return;
Expand Down Expand Up @@ -355,33 +376,6 @@ public void run() {
_id = call.argument("id");
result.success(mCBManager.getDocumentWithId(database, _id));
break;
case ("getBlobContentFromDocumentWithId"):
if (database == null) {
result.error("errDatabase", "Database with name " + dbname + "not found", null);
return;
}

if (!call.hasArgument("id") || !call.hasArgument("key") || !call.hasArgument("digest")) {
result.error("errArgs", "Database Error: Invalid Arguments", call.arguments.toString());
return;
}

_id = call.argument("id");
String _key = call.argument("key");
String _digest = call.argument("digest");
Document document = database.getDocument(_id);
byte[] content = null;

// Don't load the content if it isn't found or the digest doesn't match anymore
if (document != null) {
Blob _blob = document.getBlob(_key);
if (_blob != null && _blob.digest().equals(_digest)) {
content = _blob.getContent();
}
}

result.success(content);
break;
case ("deleteDocumentWithId"):
if (database == null) {
result.error("errDatabase", "Database with name " + dbname + "not found", null);
Expand Down

0 comments on commit 7f6106e

Please sign in to comment.