package org.geogebra.web.web.move.googledrive.operations;
import org.geogebra.common.move.events.BaseEvent;
import org.geogebra.common.move.ggtapi.events.LogOutEvent;
import org.geogebra.common.move.ggtapi.events.LoginEvent;
import org.geogebra.common.move.operations.BaseOperation;
import org.geogebra.common.move.views.BaseEventView;
import org.geogebra.common.move.views.EventRenderable;
import org.geogebra.common.util.debug.Log;
import org.geogebra.web.html5.euclidian.EuclidianViewWInterface;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.html5.move.googledrive.GoogleDriveOperation;
import org.geogebra.web.html5.util.DynamicScriptElement;
import org.geogebra.web.html5.util.JSON;
import org.geogebra.web.web.gui.dialog.DialogManagerW;
import org.geogebra.web.web.move.googledrive.events.GoogleDriveLoadedEvent;
import org.geogebra.web.web.move.googledrive.events.GoogleLogOutEvent;
import org.geogebra.web.web.move.googledrive.events.GoogleLoginEvent;
import org.geogebra.web.web.move.googledrive.models.GoogleDriveModelW;
import org.geogebra.web.web.util.SaveCallback;
import org.geogebra.web.web.util.SaveCallback.SaveState;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.Window.Location;
/**
* @author gabor
*
* Operational class for Google Drive Api
*/
public class GoogleDriveOperationW extends BaseOperation<EventRenderable>
implements EventRenderable, GoogleDriveOperation {
private static final String GoogleApiJavaScriptSrc = "https://apis.google.com/js/client.js?onload=GGW_loadGoogleDrive";
private boolean isDriveLoaded;
private AppW app;
private boolean loggedIn;
private JavaScriptObject googleDriveURL;
private String authToken;
private boolean needsPicker;
private String driveBase64description = null;
private String driveBase64FileName = null;
private Runnable waitingHandler;
/**
* creates new google drive operation instance
*
* @param app
* Application
*/
public GoogleDriveOperationW(AppW app) {
this.app = app;
setCurrentFileId();
setView(new BaseEventView());
setModel(new GoogleDriveModelW());
app.getLoginOperation().getView().add(this);
getView().add(this);
}
public String getFileName() {
return driveBase64FileName;
}
public String getFileDescription() {
return driveBase64description;
}
@Override
public GoogleDriveModelW getModel() {
return (GoogleDriveModelW) super.getModel();
}
/**
* @return the logged in user name
*/
public String getUserName() {
return getModel().getUserName();
}
private boolean inited = false;
/**
* Go for the google drive url, and fetch the script
*/
@Override
public void initGoogleDriveApi() {
if (!inited) {
createGoogleApiCallbackFunction();
fetchScript();
inited = true;
}
}
private static void fetchScript() {
DynamicScriptElement script = (DynamicScriptElement) Document.get()
.createScriptElement();
script.setSrc(GoogleApiJavaScriptSrc);
Document.get().getBody().appendChild(script);
}
private native void createGoogleApiCallbackFunction() /*-{
var _this = this;
$wnd.GGW_loadGoogleDrive = function() {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::loadGoogleDrive()();
}
}-*/;
private native void loadGoogleDrive() /*-{
var _this = this;
if ($wnd.gapi) {
$wnd.gapi.load('auth', {
'callback' : function() {
}
});
$wnd.gapi
.load(
'picker',
{
'callback' : function() {
@org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("picker loaded");
}
});
if ($wnd.gapi.client) {
$wnd.gapi.client
.load(
'drive',
'v2',
function() {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::googleDriveLoaded()();
});
}
}
}-*/;
private void googleDriveLoaded() {
this.isDriveLoaded = true;
onEvent(new GoogleDriveLoadedEvent());
}
/**
* @return if google drive loaded or not
*/
public boolean isDriveLoaded() {
return isDriveLoaded;
}
/**
* logs in the user to Google
*
* @param immediate
* wheter to force login popup open
*/
public native void login(boolean immediate) /*-{
var _this = this, config = {
'client_id' : @org.geogebra.common.GeoGebraConstants::GOOGLE_CLIENT_ID,
'scope' : @org.geogebra.common.GeoGebraConstants::DRIVE_SCOPE
+ " "
+ @org.geogebra.common.GeoGebraConstants::USERINFO_EMAIL_SCOPE
+ " "
+ @org.geogebra.common.GeoGebraConstants::USERINFO_PROFILE_SCOPE
+ " "
+ @org.geogebra.common.GeoGebraConstants::PLUS_ME_SCOPE,
'immediate' : immediate
};
//config.max_auth_age = 0;
$wnd.gapi.auth
.authorize(
config,
function(resp) {
var token = resp ? resp.access_token : {};
var error = resp ? resp.error : "";
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::authorizeCallback(Ljava/lang/String;Ljava/lang/String;)(token,error);
}
);
}-*/;
private void authorizeCallback(String token, String error) {
if (error != null && error.length() > 0) {
Log.debug("GOOGLE LOGIN" + error);
this.loggedIn = false;
onEvent(new GoogleLoginEvent(false));
} else {
this.loggedIn = true;
this.authToken = token;
if (this.needsPicker) {
this.needsPicker = false;
createPicker(authToken);
} else if (this.waitingHandler != null) {
waitingHandler.run();
}
onEvent(new GoogleLoginEvent(true));
}
}
private native void createPicker(String token2) /*-{
var _this = this;
var picker = new $wnd.google.picker.PickerBuilder()
.addView($wnd.google.picker.ViewId.DOCS)
.addView($wnd.google.picker.ViewId.FOLDERS)
.setOAuthToken(token2)
.setDeveloperKey("AIzaSyBZlOTdZmzNrXZy2QIrDEz8uXJ9lOUFGE0")
.setCallback(
function(data) {
if (data.action != "picked" || data.docs.length < 1) {
return;
}
var request = $wnd.gapi.client.drive.files.get({
fileId : data.docs[0].id
});
request
.execute(function(resp) {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::loadFromGoogleFile(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(resp.downloadUrl, resp.description, resp.title, resp.id);
});
}).build();
picker.setVisible(true);
}-*/;
@Override
public void renderEvent(BaseEvent event) {
Log.debug("event: " + event.toString());
if (event instanceof GoogleDriveLoadedEvent) {
checkIfOpenedFromGoogleDrive();
return;
}
if (event instanceof GoogleLoginEvent) {
if (((GoogleLoginEvent) event).isSuccessFull()) {
checkIfFileMustbeOpenedFromGoogleDrive();
} else {
if ("open".equals(getAction())) {
login(false);
} else if (getModel().lastLoggedInFromGoogleDrive()) {
login(false);
}
}
return;
}
if (event instanceof LoginEvent) {
if (((LoginEvent) event).isSuccessful()) {
if (app.getLoginOperation().getModel().getLoggedInUser()
.hasGoogleDrive()) {
// do nothing, see requestDriveLogin
} else {
GoogleDriveOperationW.this.getModel()
.setLoggedInFromGoogleDrive(false);
}
} else {
logOut();
}
return;
}
if (event instanceof LogOutEvent) {
logOut();
return;
}
}
public void requestDriveLogin() {
if (this.isDriveLoaded) {
this.login(true);
this.getModel().setLoggedInFromGoogleDrive(true);
} else {
getView().add(new EventRenderable() {
@Override
public void renderEvent(BaseEvent loadevent) {
if (loadevent instanceof GoogleDriveLoadedEvent) {
login(true);
GoogleDriveOperationW.this.getModel()
.setLoggedInFromGoogleDrive(true);
}
}
});
}
}
public native void initFileNameItems(GoogleDriveFileHandler fh) /*-{
var fileChooser = this;
fh.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveFileHandler::clearMaterials()();
function retrieveAllFiles(callback) {
var retrievePageOfFiles = function(request, result) {
request.execute(function(resp) {
result = result.concat(resp.items);
var nextPageToken = resp.nextPageToken;
if (nextPageToken) {
request = $wnd.gapi.client.drive.files.list({
'pageToken': nextPageToken
});
retrievePageOfFiles(request, result);
} else {
callback(result);
}
});
}
var initialRequest = $wnd.gapi.client.drive.files.list();
retrievePageOfFiles(initialRequest, []);
}
retrieveAllFiles(function(resp) {
resp.forEach(function(value, index, array) {
if (value.mimeType === "application/vnd.geogebra.file" ||
value.fileExtension === "ggb" ||
(value.title.lastIndexOf(".ggb") > -1)) {
fh.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveFileHandler::show(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)
(value.title,value.lastModifyingUserName,new Date(value.modifiedDate).getTime()+"", value.downloadUrl, value.description, value.id, value.thumbnailLink);
}
});
fh.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveFileHandler::done()();
});
}-*/;
private void checkIfFileMustbeOpenedFromGoogleDrive() {
if ("open".equals(getAction())) {
openFileFromGoogleDrive(googleDriveURL);
}
}
private native void openFileFromGoogleDrive(JavaScriptObject descriptors) /*-{
var id = descriptors["ids"] ? descriptors["ids"][0] : undefined, _this = this;
request;
if (id !== undefined) {
request = $wnd.gapi.client.drive.files.get({
fileId : id
});
request
.execute(function(resp) {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::loadFromGoogleFile(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(resp.downloadUrl, resp.description, resp.title, resp.id);
});
}
}-*/;
/**
* @return if the user is logged into google
*/
public boolean isLoggedIntoGoogle() {
return loggedIn;
}
/**
* @param currentFileName
* name of the file
* @param description
* description of the file
* @param title
* title
* @param id
* id of the file
*/
public native void loadFromGoogleFile(String currentFileName,
String description, String title, String id) /*-{
var _this = this;
function downloadFile(downloadUrl, callback) {
if (downloadUrl) {
var accessToken = $wnd.gapi.auth.getToken().access_token;
var xhr = new $wnd.XMLHttpRequest();
xhr.open('GET', downloadUrl);
xhr.responseType = "blob";
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.onload = function() {
callback(xhr.response);
};
xhr.onerror = function() {
callback(null);
};
xhr.send();
} else {
callback(null);
}
}
downloadFile(
currentFileName,
function(content) {
var reader = new FileReader();
reader
.addEventListener(
"loadend",
function(e) {
if (e.target.result.indexOf("UEsDBBQ") === 0) {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::processGoogleDriveFileContentAsBase64(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(e.target.result, description, title, id);
} else {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::processGoogleDriveFileContentAsBinary(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(content, description, title, id);
}
});
reader.readAsText(content);
});
}-*/;
private void processGoogleDriveFileContentAsBase64(String base64,
String description, String title, String id) {
app.loadGgbFileAsBase64Again(base64);
postprocessFileLoading(description, title, id);
}
private void postprocessFileLoading(String description, String title,
String id) {
refreshCurrentFileDescriptors(title, description);
setCurrentFileId(id);
app.setUnsaved();
}
private void processGoogleDriveFileContentAsBinary(JavaScriptObject binary,
String description, String title, String id) {
app.loadGgbFileAsBinaryAgain(binary);
postprocessFileLoading(description, title, id);
}
@Override
public void refreshCurrentFileDescriptors(String fName, String desc) {
if ("null".equals(desc) || "undefined".equals(desc)) {
driveBase64description = "";
} else {
driveBase64description = desc;
}
driveBase64FileName = fName;
}
/**
* logs out from Google Drive (this means, removes the possibilities to
* interact with Google Drive)
*/
public void logOut() {
this.onEvent(new GoogleLogOutEvent());
this.getModel().setLoggedInFromGoogleDrive(false);
}
/**
* @param fileName
* name of the File
* @param description
* Description of the file
* @return javascript function to called back;
*/
public native JavaScriptObject getPutFileCallback(String fileName,
String description, boolean isggb) /*-{
var _this = this;
return function(base64) {
var fName = fileName, ds = description;
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::saveFileToGoogleDrive(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)(fName,ds,base64,isggb);
};
}-*/;
private void saveFileToGoogleDrive(final String fileName,
final String description, final String fileContent,
boolean isggb) {
JavaScriptObject metaData = JavaScriptObject.createObject();
JSON.put(metaData, "title", fileName);
JSON.put(metaData, "description", description);
if (!fileName.equals(getFileName())) {
setCurrentFileId(null);
}
if ((getFolderId() != null) && !"".equals(getFolderId())) {
JavaScriptObject folderId = JavaScriptObject.createObject();
JSON.put(folderId, "id", getFolderId());
JsArray<JavaScriptObject> parents = (JsArray<JavaScriptObject>) JavaScriptObject
.createArray();
parents.push(folderId);
JSON.put(metaData, "parents", parents);
}
JavaScriptObject thumbnail = JavaScriptObject.createObject();
JSON.put(
thumbnail,
"image",
((EuclidianViewWInterface) app.getActiveEuclidianView())
.getCanvasBase64WithTypeString()
.substring("data:image/png;base64,".length())
.replace("+", "-").replace("/", "_"));
JSON.put(thumbnail, "mimeType", "image/png");
JSON.putObject(metaData, "thumbnail", thumbnail);
Log.debug(metaData);
handleFileUploadToGoogleDrive(getCurrentFileId(), metaData, fileContent,
isggb);
}
private native void handleFileUploadToGoogleDrive(String id,
JavaScriptObject metaData, String base64, boolean isggb) /*-{
var _this = this, fId = id ? id : "";
function updateFile(fileId, fileMetadata, fileData) {
var boundary = '-------314159265358979323846';
var delimiter = "\r\n--" + boundary + "\r\n";
var close_delim = "\r\n--" + boundary + "--";
var contentType = @org.geogebra.common.GeoGebraConstants::GGW_MIME_TYPE;
var base64Data = fileData;
var multipartRequestBody = delimiter
+ 'Content-Type: application/json\r\n\r\n'
+ JSON.stringify(fileMetadata) + delimiter
+ 'Content-Type: ' + contentType + '\r\n'
+ 'Content-Transfer-Encoding: base64\r\n' + '\r\n'
+ base64Data + close_delim;
var method = (fileId ? 'PUT' : 'POST');
var request = $wnd.gapi.client.request({
'path' : '/upload/drive/v2/files/' + fileId,
'method' : method,
'params' : {
'uploadType' : 'multipart',
'alt' : 'json'
},
'headers' : {
'Content-Type' : 'multipart/mixed; boundary="' + boundary
+ '"'
},
'body' : multipartRequestBody
});
request
.execute(function(resp) {
if (!resp.error) {
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::updateAfterGoogleDriveSave(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)(resp.id, resp.title, resp.description, isggb)
} else {
@org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("Error saving to Google Drive: " + resp.error);
_this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::showUploadError()();
}
});
}
updateFile(fId, metaData, base64);
}-*/;
private void showUploadError() {
((DialogManagerW) app.getDialogManager()).getSaveDialog().hide();
((DialogManagerW) app.getDialogManager()).showAlertDialog(app
.getLocalization().getMenu("GoogleDriveSaveProblem"));
}
private void updateAfterGoogleDriveSave(String id, String fileName,
String description, boolean isggb) {
((DialogManagerW) app.getDialogManager()).getSaveDialog()
.runAfterSaveCallback();
((DialogManagerW) app.getDialogManager()).getSaveDialog().hide();
SaveCallback.onSaved(app, SaveState.OK, !isggb);
if (isggb) {
refreshCurrentFileDescriptors(fileName, description);
setCurrentFileId(id);
}
}
private void checkIfOpenedFromGoogleDrive() {
String state = Location.getParameter("state");
Log.debug(state);
if (state != null && !"".equals(state)) {
googleDriveURL = JSON.parse(state);
Log.debug(googleDriveURL);
if (!this.loggedIn) {
login(true);
}
}
}
private String getFolderId() {
String folderId = null;
if (googleDriveURL != null) {
folderId = JSON.get(googleDriveURL, "folderId");
}
return folderId;
}
private String getAction() {
String action = null;
if (googleDriveURL != null) {
action = JSON.get(googleDriveURL, "action");
}
return action;
}
@Override
public void requestPicker() {
if (this.authToken != null) {
createPicker(this.authToken);
} else {
this.needsPicker = true;
login(false);
}
}
@Override
public void resetStorageInfo() {
driveBase64FileName = null;
driveBase64description = null;
currentFileId = null;
}
private String currentFileId = null;
public String getCurrentFileId() {
return currentFileId;
}
public void setCurrentFileId(String currentFileId) {
this.currentFileId = currentFileId;
}
protected native void setCurrentFileId() /*-{
if ($wnd.GGW_appengine) {
this.@org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW::currentFileId = $wnd.GGW_appengine.FILE_IDS[0];
}
}-*/;
@Override
public void afterLogin(Runnable todo) {
if (this.isLoggedIntoGoogle()) {
todo.run();
} else {
this.waitingHandler = todo;
login(false);
}
}
}