Skip to content

Commit

Permalink
Make the backpack persistent
Browse files Browse the repository at this point in the history
We store the backpack in a new “user file” named backpack.xml (the other
user file supported is “android.keystore” for the user’s signing
key). As an optimization we remove the backpack.xml file if we store
“[]” in it (the empty backpack represented as an empty JSON string).

Change-Id: I16fb747f31e59d30535f690c1b35c80bde53629f
  • Loading branch information
jisqyv committed Feb 14, 2017
1 parent e5b0d1e commit 3548e1d
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 34 deletions.
14 changes: 14 additions & 0 deletions appinventor/appengine/src/com/google/appinventor/client/Ode.java
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,20 @@ public void onFailure(Throwable caught) {

userInfoService.getSystemConfig(sessionId, callback);

// We fetch the user's backpack here. This runs asynchronously with the rest
// of the system initialization.

userInfoService.getUserBackpack(new AsyncCallback<String>() {
@Override
public void onSuccess(String backpack) {
BlocklyPanel.setBackpack(backpack, false);
}
@Override
public void onFailure(Throwable caught) {
OdeLog.log("Fetching backpack failed");
}
});

History.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.appinventor.client.editor.simple.SimpleComponentDatabase;
import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener;
import com.google.appinventor.client.output.OdeLog;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.appinventor.components.common.YaVersion;

import com.google.common.collect.Maps;
Expand Down Expand Up @@ -239,8 +240,21 @@ public static boolean blocksInited(String formName) {
public static String getBackpack() {
return backpack;
}
public static void setBackpack(String bp_contents) {
public static void setBackpack(String bp_contents, boolean doStore) {
backpack = bp_contents;
if (doStore) {
Ode.getInstance().getUserInfoService().storeUserBackpack(backpack,
new AsyncCallback<Void>() {
@Override
public void onSuccess(Void nothing) {
// Nothing to do...
}
@Override
public void onFailure(Throwable caught) {
OdeLog.elog("Failed setting the backpack");
}
});
}
}

/**
Expand Down Expand Up @@ -901,7 +915,7 @@ private static native void exportMethodsToJavascript() /*-{
$wnd.BlocklyPanel_getBackpack =
$entry(@com.google.appinventor.client.editor.youngandroid.BlocklyPanel::getBackpack());
$wnd.BlocklyPanel_setBackpack =
$entry(@com.google.appinventor.client.editor.youngandroid.BlocklyPanel::setBackpack(Ljava/lang/String;));
$entry(@com.google.appinventor.client.editor.youngandroid.BlocklyPanel::setBackpack(Ljava/lang/String;Z));
}-*/;

private native void initJS() /*-{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.google.appinventor.shared.rpc.user.Config;
import com.google.appinventor.shared.rpc.user.User;
import com.google.appinventor.shared.rpc.user.UserInfoService;
import com.google.appinventor.shared.storage.StorageUtil;

/**
* Implementation of the user information service.
Expand Down Expand Up @@ -67,6 +68,20 @@ public Config getSystemConfig(String sessionId) {
return config;
}

/**
* Returns the user's backpack as an XML string.
*
* @return backpack
*/
@Override
public String getUserBackpack() {
if (!hasUserFile(StorageUtil.USER_BACKPACK_FILENAME)) {
return "[]";
} else {
return storageIo.downloadUserFile(userInfoProvider.getUserId(), StorageUtil.USER_BACKPACK_FILENAME, "UTF-8");
}
}

/**
* Returns user information.
*
Expand Down Expand Up @@ -108,6 +123,16 @@ public String loadUserSettings() {
return storageIo.loadSettings(userInfoProvider.getUserId());
}

/**
* Stores the user's backpack as an xml string
* @param backpack the xml string representing the backpack
*/

@Override
public void storeUserBackpack(String backpack) {
storageIo.uploadUserFile(userInfoProvider.getUserId(), StorageUtil.USER_BACKPACK_FILENAME, backpack, "UTF-8");
}

/**
* Stores the user's settings.
* @param settings user's settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import java.io.UnsupportedEncodingException;
import java.nio.channels.Channels;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
Expand Down Expand Up @@ -1225,12 +1226,35 @@ public void run(Objectify datastore) {
}

/*
* We expect the UserFileData object for the given userId and fileName to
* already exist in the datastore. Find the object and update its contents.
* We look for the UserFileData object for the given userId and fileName.
* If it doesn't exit, we create it.
*
* SPECIAL CASE: If fileName == StorageUtil.USER_BACKBACK_FILENAME and the
* content is "[]", we *delete* the file because the default value returned
* if the file doesn't exist is "[]" (the JSON empty list). This is to reduce
* the clutter of files for the case where someone doesn't have anything in
* the backpack. We pay $$ for storage.
*
*/
private void addUserFileContents(Objectify datastore, String userId, String fileName, byte[] content) {
UserFileData ufd = datastore.find(userFileKey(userKey(userId), fileName));
Preconditions.checkState(ufd != null);
byte [] empty = new byte[] { (byte)0x5b, (byte)0x5d }; // "[]" in bytes
if (ufd == null) { // File doesn't exist
if (fileName.equals(StorageUtil.USER_BACKPACK_FILENAME) &&
Arrays.equals(empty, content)) {
return; // Nothing to do
}
ufd = new UserFileData();
ufd.fileName = fileName;
ufd.userKey = userKey(userId);
} else {
if (fileName.equals(StorageUtil.USER_BACKPACK_FILENAME) &&
Arrays.equals(empty, content)) {
// Storing an empty backback, just delete the file
datastore.delete(userFileKey(userKey(userId), fileName));
return;
}
}
ufd.content = content;
datastore.put(ufd);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public interface UserInfoService extends RemoteService {

Config getSystemConfig(String sessionId);

/**
* Retrieve's the stored Backpack
*
* @return the backpack as an xml string
*/
String getUserBackpack();

/**
* Retrieves information about the current user
*
Expand All @@ -48,6 +55,12 @@ public interface UserInfoService extends RemoteService {
*/
String loadUserSettings();

/**
* Store the user's backpack
* @param backpack string containing the backpack xml
*/
void storeUserBackpack(String backpack);

/**
* Stores the user's settings.
* @param settings user's settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public interface UserInfoServiceAsync {
*/
void getSystemConfig(String sessionId, AsyncCallback<Config> callback);

/**
* @see UserInfoService#getUserBackpack()
*/
void getUserBackpack(AsyncCallback<String> callback);

/**
* @see UserInfoService#getUserInformation()
*/
Expand All @@ -36,6 +41,11 @@ public interface UserInfoServiceAsync {
*/
void loadUserSettings(AsyncCallback<String> callback);

/**
* @see UserInfoService#storeUserBackpack(String)
*/
void storeUserBackpack(String backpack, AsyncCallback<Void> callback);

/**
* @see UserInfoService#storeUserSettings(String)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ private StorageUtil() {}
public static final long INITIAL_MOTD_ID = 1;

public static final String ANDROID_KEYSTORE_FILENAME = "android.keystore";
public static final String USER_BACKPACK_FILENAME = "backpack.xml";

/**
* Gets the final component from a path. This assumes that path components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,6 @@ public void testUploadBeforeAdd() throws BlocksTruncatedException {
}
}

public void testUploadUserFileBeforeAdd() {
final String USER_ID = "900";
final String USER_EMAIL = "[email protected]";
storage.getUser(USER_ID, USER_EMAIL);
try {
storage.uploadUserFile(USER_ID, FILE_NAME1, "does not matter",
StorageUtil.DEFAULT_CHARSET);
fail("Allowed upload before add");
} catch (IllegalStateException ignored) {
// File upload should be preceded by add
}
try {
storage.uploadRawUserFile(USER_ID, FILE_NAME2, "does not matter".getBytes());
fail("Allowed upload before add");
} catch (IllegalStateException ignored) {
// File upload should be preceded by add
}
}

public void testMuliRoleFile() {
final String USER_ID = "1000";
final String USER_EMAIL = "[email protected]";
Expand Down
19 changes: 11 additions & 8 deletions appinventor/blocklyeditor/src/backpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,18 +290,21 @@ Blockly.Backpack.prototype.addAllToBackpack = function() {
var allBlocks = Blockly.mainWorkspace.getAllBlocks();
var topBlocks = Blockly.mainWorkspace.getTopBlocks(false);
for (var x = 0; x < topBlocks.length; x++) {
block = allBlocks[x];
this.addToBackpack(block);
block = allBlocks[x];
this.addToBackpack(block, false);
}
// We have to read back the backpack (getBackpack) and store it again
// this time stating that it should be pushed up to the server
this.setBackpack(this.getBackpack(), true); // A little klunky but gets the job done
}

/**
* The backpack is an array containing 0 or more
* blocks
*/
Blockly.Backpack.prototype.addToBackpack = function(block) {
Blockly.Backpack.prototype.addToBackpack = function(block, store) {
if (this.getBackpack() == undefined) {
this.setBackpack(JSON.stringify([]));
this.setBackpack(JSON.stringify([]), false);
}

// Copy is made of the expanded block.
Expand All @@ -321,7 +324,7 @@ Blockly.Backpack.prototype.addToBackpack = function(block) {
var len = bp_contents.length;
var newBlock = "<xml>" + Blockly.Xml.domToText(xmlBlock) + "</xml>";
bp_contents[len] = newBlock;
this.setBackpack(JSON.stringify(bp_contents));
this.setBackpack(JSON.stringify(bp_contents), store);
this.grow();
Blockly.playAudio('backpack');

Expand Down Expand Up @@ -555,7 +558,7 @@ Blockly.Backpack.prototype.shrink = function() {
*/
Blockly.Backpack.prototype.clear = function() {
if (Blockly.mainWorkspace.backpack.confirmClear()) {
this.setBackpack(JSON.stringify([]));
this.setBackpack(JSON.stringify([]), true);
this.shrink();
}
}
Expand All @@ -578,7 +581,7 @@ Blockly.Backpack.prototype.getBackpack = function() {
return window.parent.BlocklyPanel_getBackpack();
}

Blockly.Backpack.prototype.setBackpack = function(backpack) {
window.parent.BlocklyPanel_setBackpack(backpack);
Blockly.Backpack.prototype.setBackpack = function(backpack, store) {
window.parent.BlocklyPanel_setBackpack(backpack, store);
}

4 changes: 2 additions & 2 deletions appinventor/lib/blockly/src/core/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ Blockly.Block.prototype.onMouseUp_ = function(e) {
var backpack = this_.workspace.backpack
// var xy = this.getRelativeToSurfaceXY();
goog.Timer.callOnce(backpack.close, 100, backpack);
backpack.addToBackpack(Blockly.selected);
backpack.addToBackpack(Blockly.selected, true);
Blockly.mainWorkspace.backpack.onMouseUp(e, Blockly.selected.startDragMouseX, Blockly.selected.startDragMouseY);
}
if (Blockly.highlightedConnection_) {
Expand Down Expand Up @@ -858,7 +858,7 @@ Blockly.Block.prototype.showContextMenu_ = function(e) {
callback: function() {
if (Blockly.selected && Blockly.selected.isDeletable() &&
Blockly.selected.workspace == Blockly.mainWorkspace) {
Blockly.mainWorkspace.backpack.addToBackpack(Blockly.selected);
Blockly.mainWorkspace.backpack.addToBackpack(Blockly.selected, true);
}
}
};
Expand Down

0 comments on commit 3548e1d

Please sign in to comment.