// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2015 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.editor.simple.components;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.DesignToolbar;
import com.google.appinventor.client.Ode;
import com.google.appinventor.client.editor.FileEditor;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.client.output.OdeLog;
import com.google.appinventor.client.utils.MessageDialog;
import com.google.appinventor.client.widgets.properties.EditableProperty;
import com.google.appinventor.shared.rpc.components.FirebaseAuthService;
import com.google.appinventor.shared.rpc.components.FirebaseAuthServiceAsync;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
/**
* Mock for the non-visible FirebaseDB component. This needs a separate mock
* from other non-visible components so that some of its properties can be
* given dynamic default values.
*
* @author will2596@gmail.com (William Byrne)
* @author jis@mit.edu (Jeffrey I. Schiller)
*/
public class MockFirebaseDB extends MockNonVisibleComponent {
public static final String TYPE = "FirebaseDB";
private static final String PROPERTY_NAME_DEVELOPER_BUCKET = "DeveloperBucket";
private static final String PROPERTY_NAME_PROJECT_BUCKET = "ProjectBucket";
private static final String PROPERTY_NAME_FIREBASE_TOKEN = "FirebaseToken";
private static final String PROPERTY_NAME_FIREBASE_URL = "FirebaseURL";
private static final String PROPERTY_NAME_DEFAULT_URL = "DefaultURL";
private static final FirebaseAuthServiceAsync AUTH_SVC = GWT.create(FirebaseAuthService.class);
private static boolean warningGiven = false; // Whether or not we have given experimental warning
private boolean persistToken = false;
/**
* Creates a new instance of a non-visible component whose icon is
* loaded dynamically (not part of the icon image bundle)
*
* @param editor
* @param type
* @param iconImage
*/
public MockFirebaseDB(SimpleEditor editor, String type, Image iconImage) {
super(editor, type, iconImage);
}
/**
* Initializes the "ProjectBucket", "DeveloperBucket", "FirebaseToken"
* properties dynamically.
*
* @param widget the iconImage for the MockFirebaseDB
*/
@Override
public final void initComponent(Widget widget) {
super.initComponent(widget);
String devBucket = Ode.getInstance().getUser().getUserEmail().replace(".", ":") + "";
DesignToolbar.DesignProject currentProject = Ode.getInstance().getDesignToolbar().getCurrentProject();
String projectName = "";
if (currentProject != null) {
projectName = currentProject.name;
}
// We use the "super" for the developer bucket here because we
// override the changeProperty method to ignore setting the developer
// bucket. The idea here is to set it here to a value based on the
// person's userid (email address) but fail to load whatever value
// is in the project file. By doing this people can put projects
// in the Gallery which use the FirebaseDB component and have the
// developer bucket get changed to the current user when they download
// the project from the Gallery.
super.changeProperty(PROPERTY_NAME_DEVELOPER_BUCKET, devBucket + "/");
changeProperty(PROPERTY_NAME_PROJECT_BUCKET, projectName);
// The default URL is loaded from the system config which in turn is
// is loaded from the "Flag" module which reads properties from
// appengine-web.xml (in the App Engine version). The standalone version
// stores it in appinventor.xml (at least for now)
String defaultURL = Ode.getInstance().getSystemConfig().getFirebaseURL();
changeProperty(PROPERTY_NAME_DEFAULT_URL, defaultURL);
OdeLog.log("Default Firebase URL = " + defaultURL);
AsyncCallback<String> callback = new AsyncCallback<String>() {
@Override
public void onSuccess(String JWT) {
String oldJWT = getPropertyValue(PROPERTY_NAME_FIREBASE_TOKEN);
String FirebaseURL = getPropertyValue(PROPERTY_NAME_FIREBASE_URL);
// Only set a new token if one wasn't already loaded OR if
// we are using the default FirebaseURL in which case the new
// token will always be correct, potentially replacing a bad
// value if a project is imported that originally was exported
// by a different user
//
// Normally this code is invoked early in component creation
// default values are then set here. If a project is being
// loaded, the properties saved in the project file will
// be loaded later. However in this case, we are in a
// callback which is often invoked *after* the project has
// been loaded. We don't want to over-write a user supplied
// token, so we only store then one we fetched if there isn't
// one already there. This is a bit of a kludge, but I haven't
// figured out a better way without making larger scale
// alterations to Mock instantiation --Jeff
if (oldJWT.equals("") || FirebaseURL.equals("DEFAULT")) {
changeProperty(PROPERTY_NAME_FIREBASE_TOKEN, JWT);
onPropertyChange(PROPERTY_NAME_FIREBASE_TOKEN, JWT);
}
}
@Override
public void onFailure(Throwable caught) {
OdeLog.elog("Failed to create FirebaseDB JWT!");
}
};
AUTH_SVC.getToken(projectName, callback);
}
/**
* Called when the component is dropped in the Designer window
* we give a warning that firebase is still experimental.
*/
@Override
public void onCreateFromPalette() {
if (!warningGiven) {
warningGiven = true;
MessageDialog.messageDialog(MESSAGES.warningDialogTitle(),
MESSAGES.firebaseExperimentalWarning(),
MESSAGES.okButton(), null, null);
}
}
/**
* Enforces the invisibility of the "DeveloperBucket" and "FirebaseToken"
* properties.
*
* @param propertyName the name of the property to check
* @return true for a visible property, false for an invisible property
*/
@Override
protected boolean isPropertyVisible(String propertyName) {
return !propertyName.equals(PROPERTY_NAME_DEVELOPER_BUCKET)
&& !propertyName.equals(PROPERTY_NAME_DEFAULT_URL)
&& super.isPropertyVisible(propertyName);
}
/**
* Changes the value of a component property.
* This version ignores the developer bucket. So its value
* is *not* loaded from the project
*
* @param name property name
* @param value new property value
*
*/
@Override
public void changeProperty(String name, String value) {
if (name.equals(PROPERTY_NAME_DEVELOPER_BUCKET)) {
return;
} else if (name.equals(PROPERTY_NAME_FIREBASE_URL)) {
onPropertyChange(name, value);
}
super.changeProperty(name, value);
}
// We provide our own onPropertyChange to catch the case
// where the FirebaseURL is changed to/from the DEFAULT value.
// This effects the persistability (is that a word?) of the FirebaseToken
// property. If we are using a private Firebase Account, we want to persis
// the FirebaseToken. Otherwise we do not.
@Override
public void onPropertyChange(String propertyName, String newValue) {
if (propertyName.equals(PROPERTY_NAME_FIREBASE_URL)) {
// If this is the DEFAULT URL, then make the FirebaseToken property
// non-persistant, but output it in YAIL
EditableProperty firebaseToken = properties.getProperty(PROPERTY_NAME_FIREBASE_TOKEN);
int tokenType = firebaseToken.getType();
if (newValue.equals("DEFAULT")) {
persistToken = false;
tokenType |= EditableProperty.TYPE_NONPERSISTED;
tokenType |= EditableProperty.TYPE_DOYAIL;
} else {
tokenType &= ~EditableProperty.TYPE_NONPERSISTED;
persistToken = true;
}
firebaseToken.setType(tokenType);
// Need to fire this change to mark the form dirty when we change the type
// of the property
onPropertyChange(PROPERTY_NAME_FIREBASE_TOKEN, firebaseToken.getValue());
}
super.onPropertyChange(propertyName, newValue);
}
/**
* Arranges that the Developer Bucket property is not persisted.
* We only use this property when the Firebase URL is set to its default
* value. In this case the developer bucket MUST be set to an value
* which is based on the current logged in use. Only this user can fetch
* a Firebase token which will grant access to the objects in the
* developer bucket.
*
* If the user sets up their own Firebase account, this property is
* not used at all.
*
* We also do not persist the FirebaseToken iff we are using the
* default Firebase account.
*
*/
@Override
public boolean isPropertyPersisted(String propertyName) {
if (propertyName.equals(PROPERTY_NAME_DEVELOPER_BUCKET) ||
propertyName.equals(PROPERTY_NAME_DEFAULT_URL)) {
return false;
} else if (propertyName.equals(PROPERTY_NAME_FIREBASE_TOKEN)) {
// We keep track of whether or not to persist the FirebaseToken property
return persistToken;
} else {
return super.isPropertyPersisted(propertyName);
}
}
@Override
public boolean isPropertyforYail(String propertyName) {
if (propertyName.equals(PROPERTY_NAME_DEVELOPER_BUCKET) ||
(propertyName.equals(PROPERTY_NAME_FIREBASE_TOKEN)) ||
(propertyName.equals(PROPERTY_NAME_DEFAULT_URL))) {
return true;
}
return super.isPropertyforYail(propertyName);
}
}