// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 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.wizards;
import static com.google.appinventor.client.Ode.MESSAGES;
import java.io.File;
import com.google.appinventor.client.ErrorReporter;
import com.google.appinventor.client.Ode;
import com.google.appinventor.client.OdeAsyncCallback;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.utils.Uploader;
import com.google.appinventor.client.youngandroid.TextValidators;
import com.google.appinventor.shared.rpc.ServerLayout;
import com.google.appinventor.shared.rpc.UploadResponse;
import com.google.appinventor.shared.rpc.project.FileNode;
import com.google.appinventor.shared.rpc.project.FolderNode;
import com.google.appinventor.shared.rpc.project.ProjectNode;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidAssetNode;
import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidAssetsFolder;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
/**
* Wizard for uploading individual files.
*
*/
public class FileUploadWizard extends Wizard {
/**
* Interface for callback to execute after a file is uploaded.
*/
public static interface FileUploadedCallback {
/**
* Will be invoked after a file is uploaded.
*
* @param folderNode the upload destination folder
* @param fileNode the file just uploaded
*/
void onFileUploaded(FolderNode folderNode, FileNode fileNode);
}
/**
* Creates a new file upload wizard.
*
* @param folderNode the upload destination folder
*/
public FileUploadWizard(FolderNode folderNode) {
this(folderNode, null);
}
/**
* Creates a new file upload wizard.
*
* @param folderNode the upload destination folder
* @param fileUploadedCallback callback to be executed after upload
*/
public FileUploadWizard(final FolderNode folderNode,
final FileUploadedCallback fileUploadedCallback) {
super(MESSAGES.fileUploadWizardCaption(), true, false);
// Initialize UI
final FileUpload upload = new FileUpload();
upload.setName(ServerLayout.UPLOAD_FILE_FORM_ELEMENT);
setStylePrimaryName("ode-DialogBox");
VerticalPanel panel = new VerticalPanel();
panel.setVerticalAlignment(VerticalPanel.ALIGN_MIDDLE);
panel.add(upload);
addPage(panel);
// Create finish command (upload a file)
initFinishCommand(new Command() {
@Override
public void execute() {
String uploadFilename = upload.getFilename();
if (!uploadFilename.isEmpty()) {
final String filename = makeValidFilename(uploadFilename);
if(!TextValidators.isValidCharFilename(filename)){
createErrorDialog(MESSAGES.malformedFilenameTitle(), MESSAGES.malformedFilename(),
Error.NOFILESELECETED, folderNode, fileUploadedCallback);
return;
} else if (!TextValidators.isValidLengthFilename(filename)){
createErrorDialog(MESSAGES.filenameBadSizeTitle(), MESSAGES.filenameBadSize(),
Error.FILENAMEBADSIZE, folderNode, fileUploadedCallback);
return;
}
int nameLength = uploadFilename.length();
String fileEnd = uploadFilename.substring(nameLength-4, nameLength);
if (".aia".equals(fileEnd.toLowerCase())) {
createErrorDialog(MESSAGES.aiaMediaAssetTitle(), MESSAGES.aiaMediaAsset(),
Error.AIAMEDIAASSET, folderNode, fileUploadedCallback);
return;
}
String fn = conflictingExistingFile(folderNode, filename);
if (fn != null && !confirmOverwrite(folderNode, fn, filename)) {
return;
} else {
String fileId = folderNode.getFileId() + "/" + filename;
// We delete all the conflicting files.
for (ProjectNode child : folderNode.getChildren()) {
if (fileId.equalsIgnoreCase(child.getFileId()) && !fileId.equals(child.getFileId())) {
final ProjectNode node = child;
String filesToClose [] = { node.getFileId()};
Ode ode = Ode.getInstance();
ode.getEditorManager().closeFileEditors(node.getProjectId(), filesToClose);
ode.getProjectService().deleteFile(ode.getSessionId(),
node.getProjectId(), node.getFileId(),
new OdeAsyncCallback<Long>(
// message on failure
MESSAGES.deleteFileError()) {
@Override
public void onSuccess(Long date) {
Ode.getInstance().getProjectManager().getProject(node).deleteNode(node);
Ode.getInstance().updateModificationDate(node.getProjectId(), date);
}
});
}
}
}
ErrorReporter.reportInfo(MESSAGES.fileUploadingMessage(filename));
// Use the folderNode's project id and file id in the upload URL so that the file is
// uploaded into that project and that folder in our back-end storage.
String uploadUrl = GWT.getModuleBaseURL() + ServerLayout.UPLOAD_SERVLET + "/" +
ServerLayout.UPLOAD_FILE + "/" + folderNode.getProjectId() + "/" +
folderNode.getFileId() + "/" + filename;
Uploader.getInstance().upload(upload, uploadUrl,
new OdeAsyncCallback<UploadResponse>(MESSAGES.fileUploadError()) {
@Override
public void onSuccess(UploadResponse uploadResponse) {
switch (uploadResponse.getStatus()) {
case SUCCESS:
ErrorReporter.hide();
onUploadSuccess(folderNode, filename, uploadResponse.getModificationDate(),
fileUploadedCallback);
break;
case FILE_TOO_LARGE:
// The user can resolve the problem by
// uploading a smaller file.
ErrorReporter.reportInfo(MESSAGES.fileTooLargeError());
break;
default:
ErrorReporter.reportError(MESSAGES.fileUploadError());
break;
}
}
});
} else {
createErrorDialog(MESSAGES.noFileSelectedTitle(), MESSAGES.noFileSelected(),
Error.NOFILESELECETED, folderNode, fileUploadedCallback);
}
}
});
}
@Override
public void show() {
super.show();
int width = 320;
int height = 40;
this.center();
setPixelSize(width, height);
super.setPagePanelHeight(40);
}
private String makeValidFilename(String uploadFilename) {
// Strip leading path off filename.
// We need to support both Unix ('/') and Windows ('\\') separators.
String filename = uploadFilename.substring(
Math.max(uploadFilename.lastIndexOf('/'), uploadFilename.lastIndexOf('\\')) + 1);
// We need to strip out whitespace from the filename.
filename = filename.replaceAll("\\s", "");
return filename;
}
private String conflictingExistingFile(FolderNode folderNode, String filename) {
String fileId = folderNode.getFileId() + "/" + filename;
for (ProjectNode child : folderNode.getChildren()) {
if (fileId.equalsIgnoreCase(child.getFileId())) {
// we want to return kitty.png rather than assets/kitty.png
return lastPathComponent(child.getFileId());
}
}
return null;
}
private String lastPathComponent (String path) {
String [] pieces = path.split("/");
return pieces[pieces.length - 1];
}
private boolean confirmOverwrite(FolderNode folderNode, String newFile, String existingFile) {
return Window.confirm(MESSAGES.confirmOverwrite(newFile, existingFile));
}
private void onUploadSuccess(final FolderNode folderNode, final String filename,
long modificationDate, final FileUploadedCallback fileUploadedCallback) {
Ode.getInstance().updateModificationDate(folderNode.getProjectId(), modificationDate);
finishUpload(folderNode, filename, fileUploadedCallback);
}
private void finishUpload(FolderNode folderNode, String filename,
FileUploadedCallback fileUploadedCallback) {
String uploadedFileId = folderNode.getFileId() + "/" + filename;
FileNode uploadedFileNode;
if (folderNode instanceof YoungAndroidAssetsFolder) {
uploadedFileNode = new YoungAndroidAssetNode(filename, uploadedFileId);
} else {
uploadedFileNode = new FileNode(filename, uploadedFileId);
}
Project project = Ode.getInstance().getProjectManager().getProject(folderNode);
uploadedFileNode = (FileNode) project.addNode(folderNode, uploadedFileNode);
if (fileUploadedCallback != null) {
fileUploadedCallback.onFileUploaded(folderNode, uploadedFileNode);
}
}
private void createErrorDialog(String title, String body, Error e,
final FolderNode folderNode, final FileUploadedCallback fileUploadedCallback) {
final DialogBox dialogBox = new DialogBox(false,true);
HTML message;
dialogBox.setStylePrimaryName("ode-DialogBox");
dialogBox.setHeight("150px");
dialogBox.setWidth("350px");
dialogBox.setGlassEnabled(true);
dialogBox.setAnimationEnabled(true);
dialogBox.center();
VerticalPanel DialogBoxContents = new VerticalPanel();
FlowPanel holder = new FlowPanel();
Button ok = new Button ("OK");
ok.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
dialogBox.hide();
new FileUploadWizard(folderNode, fileUploadedCallback).show();
}
});
holder.add(ok);
dialogBox.setText(title);
message = new HTML(body);
switch(e) {
case AIAMEDIAASSET:
Button info = new Button ("More Info");
info.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
Window.open(MESSAGES.aiaMediaAssetHelp(), "AIA Help", "");
}
});
holder.add(info);
case NOFILESELECETED:
case MALFORMEDFILENAME:
case FILENAMEBADSIZE:
default:
break;
}
message.setStyleName("DialogBox-message");
DialogBoxContents.add(message);
DialogBoxContents.add(holder);
dialogBox.setWidget(DialogBoxContents);
dialogBox.show();
}
}
enum Error {
AIAMEDIAASSET, NOFILESELECETED, MALFORMEDFILENAME, FILENAMEBADSIZE
}