Skip to content

Commit

Permalink
Auto save asset when saving record and lambda
Browse files Browse the repository at this point in the history
Refs #234.
  • Loading branch information
Steven-Chan authored Jun 15, 2018
2 parents 39cff15 + 2bc0a50 commit d211958
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 46 deletions.
9 changes: 1 addition & 8 deletions skygear/src/main/java/io/skygear/skygear/Asset.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ public class Asset {
*/
byte[] data;

/**
* The indicator whether it is pending upload.
*/
boolean pendingUpload;

/**
* The Asset MIME Type.
*/
Expand All @@ -62,7 +57,6 @@ public Asset(String name, String mimeType, byte[] data) {
this.name = name;
this.mimeType = mimeType;
this.data = data;
this.pendingUpload = true;
}

/**
Expand All @@ -78,7 +72,6 @@ public Asset(String name, String url, String mimeType) {
this.name = name;
this.url = url;
this.mimeType = mimeType;
this.pendingUpload = false;
}

/**
Expand Down Expand Up @@ -109,7 +102,7 @@ public String getUrl() {
* @return the boolean
*/
public boolean isPendingUpload() {
return pendingUpload;
return this.url == null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ public void onValidationError(Exception exception) {

@Override
public void onResponse(String response) {
this.asset.pendingUpload = false;
if (this.responseHandler != null) {
this.responseHandler.onPostSuccess(this.asset, response);
}
Expand Down
42 changes: 35 additions & 7 deletions skygear/src/main/java/io/skygear/skygear/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import android.content.Context;

import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Map;
import java.util.List;

/**
* Container for Skygear.
Expand Down Expand Up @@ -215,11 +217,24 @@ public void callLambdaFunction(String name, LambdaResponseHandler handler) {
* @param args the arguments
* @param handler the response handler
*/
public void callLambdaFunction(String name, Object[] args, LambdaResponseHandler handler) {
LambdaRequest request = new LambdaRequest(name, args);
request.responseHandler = handler;
public void callLambdaFunction(final String name, Object[] args, LambdaResponseHandler handler) {
final String lambdaName = name;
final Object[] lambdaArgs = args;
final LambdaResponseHandler responseHandler = handler;
this.publicDatabase.presave(Arrays.asList(args), new ResultCallback<List>() {
@Override
public void onSuccess(List result) {
LambdaRequest request = new LambdaRequest(name, result);
request.responseHandler = responseHandler;

this.requestManager.sendRequest(request);
Container.this.requestManager.sendRequest(request);
}

@Override
public void onFailure(Error error) {

}
});
}

/**
Expand All @@ -230,9 +245,22 @@ public void callLambdaFunction(String name, Object[] args, LambdaResponseHandler
* @param handler the response handler
*/
public void callLambdaFunction(String name, Map<String, Object> args, LambdaResponseHandler handler) {
LambdaRequest request = new LambdaRequest(name, args);
request.responseHandler = handler;
final String lambdaName = name;
final Map<String, Object> lambdaArgs = args;
final LambdaResponseHandler responseHandler = handler;
this.publicDatabase.presave(args, new ResultCallback<Map>() {
@Override
public void onSuccess(Map result) {
LambdaRequest request = new LambdaRequest(lambdaName, result);
request.responseHandler = responseHandler;

this.requestManager.sendRequest(request);
Container.this.requestManager.sendRequest(request);
}

@Override
public void onFailure(Error error) {

}
});
}
}
239 changes: 230 additions & 9 deletions skygear/src/main/java/io/skygear/skygear/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
package io.skygear.skygear;

import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;

/**
* The Skygear Database.
Expand Down Expand Up @@ -81,6 +87,149 @@ public String getName() {
return name;
}

private static <T> List<T> findInObject(Object object, Class<T> klass) {
if (klass.isInstance(object)) {
List<T> wanted = new ArrayList<T>();
wanted.add((T)object);
return wanted;
} else if (object instanceof Record) {
return Database.findInObject(((Record)object).getData(), klass);
} else if (object instanceof Map) {
List<T> wanted = new ArrayList<T>();

for (Object item : ((Map)object).values()) {
wanted.addAll(Database.findInObject(item, klass));
}
return wanted;

} else if (object instanceof List) {
List<T> wanted = new ArrayList<T>();
for (Object item : (List)object) {
wanted.addAll(Database.findInObject(item, klass));
}
return wanted;
} else if (object instanceof Object[]) {
return Database.findInObject(Arrays.asList(object), klass);
} else {
return new ArrayList<T>();
}
}

private static Object replaceObject(Object object, Map mapTable) {
if (object == null) {
return null;
} else if (mapTable.containsKey(object)) {
return mapTable.get(object);
}

if (object instanceof Record) {
return Database.replaceObject((Record)object, mapTable);
} else if (object instanceof Map) {
return Database.replaceObject((Map)object, mapTable);
} else if (object instanceof List) {
return Database.replaceObject((List)object, mapTable);
} else if (object instanceof Object[]) {
return Database.replaceObject((Object[])object, mapTable);
} else {
return object;
}
}

private static Map replaceObject(Map object, Map mapTable) {
Map newMap = new HashMap();
Iterator it = object.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
newMap.put(entry.getKey(), Database.replaceObject(entry.getValue(), mapTable));
}
return newMap;
}

private static List replaceObject(List object, Map mapTable) {
List newList = new ArrayList();
for (Object item : object) {
newList.add(Database.replaceObject(item, mapTable));
}
return newList;
}

private static Object[] replaceObject(Object[] object, Map mapTable) {
Object[] newArray = new Object[object.length];
for (int i = 0; i < newArray.length; i++) {
newArray[i] = Database.replaceObject(object[i], mapTable);
}
return newArray;
}

private static Record replaceObject(Record object, Map mapTable) {
// TODO: It is better to create a clone of the Record object, but
// the Record class does not offer a clone method.
Record record = (Record)object;
record.set((Map)Database.replaceObject(record.getData(), mapTable));
return record;
}

private void presaveAssets(final Object object, final ResultCallback<Map<Asset, Asset>> callback) {
List<Asset> assetsToUpload = new ArrayList<Asset>();
for (Asset asset : Database.findInObject(object, Asset.class)) {
if (asset.isPendingUpload()) {
assetsToUpload.add(asset);
}
}

if (assetsToUpload.size() > 0) {
this.uploadAssets(assetsToUpload, callback);
} else {
callback.onSuccess(new HashMap<Asset, Asset>());
}
}

private void presave(final Record[] object, final ResultCallback<Record[]> callback) {
this.presaveAssets(object, new ResultCallback<Map<Asset, Asset>>() {
@Override
public void onSuccess(Map<Asset, Asset> result) {
Record[] newArray = new Record[object.length];
for (int i = 0; i < object.length; i++) {
newArray[i] = Database.replaceObject(object[i], result);
}
callback.onSuccess(newArray);
}

@Override
public void onFailure(Error error) {
callback.onFailure(error);
}
});
}

void presave(final List object, final ResultCallback<List> callback) { // package-private
this.presaveAssets(object, new ResultCallback<Map<Asset, Asset>>() {
@Override
public void onSuccess(Map<Asset, Asset> result) {
callback.onSuccess(Database.replaceObject(object, result));
}

@Override
public void onFailure(Error error) {
callback.onFailure(error);
}
});
}

void presave(final Map object, final ResultCallback<Map> callback) { // package-private
this.presaveAssets(object, new ResultCallback<Map<Asset, Asset>>() {
@Override
public void onSuccess(Map<Asset, Asset> result) {
callback.onSuccess(Database.replaceObject(object, result));
}

@Override
public void onFailure(Error error) {
callback.onFailure(error);
}
});
}

/**
* Save a record.
*
Expand All @@ -97,11 +246,23 @@ public void save(Record record, RecordSaveResponseHandler handler) {
* @param records the records
* @param handler the response handler
*/
public void save(Record[] records, RecordSaveResponseHandler handler) {
RecordSaveRequest request = new RecordSaveRequest(records, this);
request.responseHandler = handler;
public void save(final Record[] records, RecordSaveResponseHandler handler) {
final Record[] recordsToSave = records;
final RecordSaveResponseHandler responseHandler = handler;
this.presave(records, new ResultCallback<Record[]>() {
@Override
public void onSuccess(Record[] result) {
RecordSaveRequest request = new RecordSaveRequest(result, Database.this);
request.responseHandler = responseHandler;

this.getContainer().sendRequest(request);
Database.this.getContainer().sendRequest(request);
}

@Override
public void onFailure(Error error) {
responseHandler.onSaveFail(error);
}
});
}

/**
Expand All @@ -120,12 +281,24 @@ public void saveAtomically(Record record, RecordSaveResponseHandler handler) {
* @param records the records
* @param handler the response handler
*/
public void saveAtomically(Record[] records, RecordSaveResponseHandler handler) {
RecordSaveRequest request = new RecordSaveRequest(records, this);
request.responseHandler = handler;
request.setAtomic(true);
public void saveAtomically(final Record[] records, RecordSaveResponseHandler handler) {
final Record[] recordsToSave = records;
final RecordSaveResponseHandler responseHandler = handler;
this.presave(records, new ResultCallback<Record[]>() {
@Override
public void onSuccess(Record[] result) {
RecordSaveRequest request = new RecordSaveRequest(result, Database.this);
request.setAtomic(true);
request.responseHandler = responseHandler;

this.getContainer().sendRequest(request);
Database.this.getContainer().sendRequest(request);
}

@Override
public void onFailure(Error error) {
responseHandler.onSaveFail(error);
}
});
}

/**
Expand Down Expand Up @@ -195,6 +368,54 @@ public void onPreparePostFail(Error error) {
requestManager.sendRequest(preparePostRequest);
}

private void uploadAssets(
final List<Asset> assets,
final ResultCallback<Map<Asset, Asset>> callback
) {
final RequestManager requestManager = this.getContainer().requestManager;

final Map<Asset, Asset> savedAssets = new HashMap<>();
final List<Error> errors = new ArrayList<Error>();
final List<Asset> allAssets = assets;
final Semaphore lock = new Semaphore(1);

for (final Asset asset : assets) {
AssetPostRequest.ResponseHandler handler = new AssetPostRequest.ResponseHandler() {
private void onAllFinished() {
if (errors.isEmpty()) {
callback.onSuccess(savedAssets);
} else {
callback.onFailure(errors.get(0));
}
}

@Override
public void onPostSuccess(Asset savedAsset, String response) {
lock.acquireUninterruptibly();
savedAssets.put(asset, savedAsset);
boolean allFinished = allAssets.size() >= savedAssets.size() + errors.size();
lock.release();
if (allFinished) {
this.onAllFinished();
}
}

@Override
public void onPostFail(Asset savedAsset, Error error) {
lock.acquireUninterruptibly();
errors.add(error);
boolean allFinished = allAssets.size() >= savedAssets.size() + errors.size();
lock.release();
if (allFinished) {
this.onAllFinished();
}
}
};

this.uploadAsset(asset, handler);
}
}

static class Factory {
/**
* Instantiates a public database.
Expand Down
Loading

0 comments on commit d211958

Please sign in to comment.