/*
* Copyright (C) 2013 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.webapp.client.widget;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.i18n.SmartGwtMessages;
import com.smartgwt.client.rpc.RPCResponse;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.Encoding;
import com.smartgwt.client.types.TitleOrientation;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.Page;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.Progressbar;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.CloseClickEvent;
import com.smartgwt.client.widgets.events.CloseClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.HiddenItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.layout.HStack;
import com.smartgwt.client.widgets.layout.VLayout;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.ClientUtils;
import cz.cas.lib.proarc.webapp.client.ErrorHandler;
import cz.cas.lib.proarc.webapp.client.ds.DigitalObjectDataSource.DigitalObject;
import cz.cas.lib.proarc.webapp.client.ds.RestConfig;
import cz.cas.lib.proarc.webapp.shared.rest.DigitalObjectResourceApi;
import java.util.HashMap;
import java.util.logging.Logger;
import org.moxieapps.gwt.uploader.client.File;
import org.moxieapps.gwt.uploader.client.Uploader;
import org.moxieapps.gwt.uploader.client.Uploader.ButtonAction;
import org.moxieapps.gwt.uploader.client.Uploader.Cursor;
import org.moxieapps.gwt.uploader.client.events.FileQueueErrorEvent;
import org.moxieapps.gwt.uploader.client.events.FileQueueErrorHandler;
import org.moxieapps.gwt.uploader.client.events.FileQueuedEvent;
import org.moxieapps.gwt.uploader.client.events.FileQueuedHandler;
import org.moxieapps.gwt.uploader.client.events.UploadCompleteEvent;
import org.moxieapps.gwt.uploader.client.events.UploadCompleteHandler;
import org.moxieapps.gwt.uploader.client.events.UploadErrorEvent;
import org.moxieapps.gwt.uploader.client.events.UploadErrorHandler;
import org.moxieapps.gwt.uploader.client.events.UploadProgressEvent;
import org.moxieapps.gwt.uploader.client.events.UploadProgressHandler;
import org.moxieapps.gwt.uploader.client.events.UploadSuccessEvent;
import org.moxieapps.gwt.uploader.client.events.UploadSuccessHandler;
/**
* The widget to browse and send a file.
*
* <p>It uses HTML5 based widget {@link Uploader} to send the file. The fallback is Flash.
*
* @author Jan Pokorsky
*/
public final class UploadFile {
private static final String FIELD_FILE = DigitalObjectResourceApi.DISSEMINATION_FILE;
private static final String FIELD_MIMETYPE = DigitalObjectResourceApi.DISSEMINATION_MIME;
private static final String FIELD_PID = DigitalObjectResourceApi.DIGITALOBJECT_PID;
private static final String FIELD_BATCHID = DigitalObjectResourceApi.BATCHID_PARAM;
private static final Logger LOG = Logger.getLogger(UploadFile.class.getName());
private final ClientMessages i18n;
private final SmartGwtMessages i18nSgwt;
private final DynamicForm form;
private Window window;
private BooleanCallback windowCallback;
private Uploader uploader;
private HStack btnLayout;
private final Progressbar progressbar;
private IButton btnOk;
private IButton btnCancel;
private DigitalObject dobj;
public UploadFile(ClientMessages i18n) {
this.i18n = i18n;
i18nSgwt = ClientUtils.createSmartGwtMessages();
progressbar = new Progressbar();
form = createForm();
}
public void showWindow(DigitalObject dobj, BooleanCallback callback) {
this.dobj = dobj;
this.windowCallback = callback;
form.setAutoHeight();
window = new Window();
window.setAutoSize(true);
window.setAutoCenter(true);
window.setIsModal(true);
window.setTitle(i18n.DigitalObjectEditor_MediaEditor_Uploader_Title());
window.setShowMinimizeButton(false);
window.setShowModalMask(true);
window.addCloseClickHandler(new CloseClickHandler() {
@Override
public void onCloseClick(CloseClickEvent event) {
closeWindow();
}
});
VLayout winContent = new VLayout(4);
winContent.setWidth(400);
winContent.setPadding(5);
winContent.addMember(createBrowseCanvas());
winContent.addMember(form);
winContent.addMember(createProgressBar());
winContent.addMember(createButtons());
window.addItem(winContent);
window.show();
}
private void uploadFile() {
JSONObject post = new JSONObject();
String mime = form.getValueAsString(FIELD_MIMETYPE);
if (mime != null && !mime.trim().isEmpty()) {
post.put(FIELD_MIMETYPE, new JSONString(mime));
}
post.put(FIELD_PID, new JSONString(dobj.getPid()));
String batchId = dobj.getBatchId();
if (batchId != null) {
post.put(FIELD_BATCHID, new JSONString(batchId));
}
post.put(DigitalObjectResourceApi.DISSEMINATION_ERROR, JSONBoolean.getInstance(true));
uploader.setPostParams(post);
uploader.startUpload();
showUploading(true);
}
private void onUploadClick() {
form.clearErrors(true);
progressbar.setPercentDone(0);
boolean valid = form.validate();
if (valid) {
uploadFile();
}
}
private void closeWindow() {
window.hide();
window.destroy();
boolean uploaded = uploader.getStats().getSuccessfulUploads() > 0;
windowCallback.execute(uploaded);
}
private void showUploading(boolean uploading) {
btnLayout.setDisabled(uploading);
}
private DynamicForm createForm() {
DynamicForm form = new DynamicForm();
form.setEncoding(Encoding.MULTIPART);
form.setBrowserSpellCheck(false);
form.setNumCols(2);
form.setTitleOrientation(TitleOrientation.TOP);
form.setCanSubmit(true);
TextItem mimeItem = new TextItem(FIELD_MIMETYPE,
i18n.DigitalObjectEditor_MediaEditor_Uploader_Mimetype_Title());
mimeItem.setWidth(400);
mimeItem.setColSpan(2);
TextItem filenameItem = new TextItem(FIELD_FILE,
i18n.DigitalObjectEditor_MediaEditor_Uploader_Filename_Title());
filenameItem.setWidth(400);
filenameItem.setColSpan(2);
filenameItem.setRequired(Boolean.TRUE);
HiddenItem pidItem = new HiddenItem(FIELD_PID);
HiddenItem batchIdItem = new HiddenItem(FIELD_BATCHID);
form.setFields(filenameItem, mimeItem, pidItem, batchIdItem);
return form;
}
private Canvas createBrowseCanvas() {
UploadHandler uploadHandler = new UploadHandler();
uploader = new Uploader() {
@Override
protected void onLoad() {
super.onLoad();
openFileDialog(uploader);
}
};
uploader.setUploadURL(RestConfig.URL_DIGOBJECT_DISSEMINATION)
.setButtonImageURL(Page.getSkinImgDir() + "MultiUploadItem/icon_add_files.png")
.setButtonWidth(16)
.setButtonHeight(16)
.setButtonCursor(Cursor.HAND)
.setButtonAction(ButtonAction.SELECT_FILE)
.setFileSizeLimit("1 GB")
.setFilePostName(FIELD_FILE)
.setFileUploadLimit(0)
.setFileQueuedHandler(uploadHandler)
.setFileQueueErrorHandler(uploadHandler)
.setUploadCompleteHandler(uploadHandler)
.setUploadErrorHandler(uploadHandler)
.setUploadProgressHandler(uploadHandler)
.setUploadSuccessHandler(uploadHandler)
;
HStack hStack = new HStack(4);
Label label = new Label(i18n.DigitalObjectEditor_MediaEditor_Uploader_Browse_Title() + ": ");
label.setAutoFit(true);
label.setWrap(false);
hStack.addMember(label);
hStack.addMember(uploader);
return hStack;
}
private void openFileDialog(Uploader uploader) {
// workaround to call Uploader.openFileDialog
for (int i = uploader.getWidgetCount() - 1; i >= 0; i--) {
Widget widget = uploader.getWidget(i);
if (widget instanceof com.google.gwt.user.client.ui.Label) {
final com.google.gwt.user.client.ui.Label btn = (com.google.gwt.user.client.ui.Label) widget;
btn.fireEvent(new ClickEvent() {});
break;
}
}
}
private class UploadHandler implements UploadCompleteHandler, UploadErrorHandler,
UploadProgressHandler, UploadSuccessHandler, FileQueuedHandler, FileQueueErrorHandler {
@Override
public boolean onUploadComplete(UploadCompleteEvent uploadCompleteEvent) {
showUploading(false);
return true;
}
@Override
public boolean onUploadError(UploadErrorEvent uploadErrorEvent) {
form.clearValues();
showFormError(uploadErrorEvent.getMessage());
return true;
}
private void showFormError(String message) {
HashMap<String, String> errors = new HashMap<String, String>();
errors.put(FIELD_FILE, message);
form.setErrors(errors, true);
}
@Override
public boolean onUploadProgress(UploadProgressEvent uploadProgressEvent) {
// uploadProgressEvent.getBytesComplete();
int percentDone = (int) uploadProgressEvent.getFile().getPercentUploaded();
progressbar.setPercentDone(percentDone);
return true;
}
@Override
public boolean onUploadSuccess(UploadSuccessEvent uploadSuccessEvent) {
String response = uploadSuccessEvent.getServerData();
DSResponse dsResponse = ErrorHandler.getDsResponse(response);
if (dsResponse.getStatus() == RPCResponse.STATUS_SUCCESS) {
btnOk.hide();
uploader.setButtonDisabled(true);
btnCancel.setTitle(i18nSgwt.dialog_DoneButtonTitle());
closeWindow();
} else {
// upload error
uploader.cancelUpload(false);
// clear the chosen file name to force user to enqueue the cancelled file again
// as there is no uploader API to do it
form.clearValues();
progressbar.setPercentDone(0);
showError(dsResponse);
}
return true;
}
private void showError(DSResponse response) {
// ClientUtils.info(LOG, "dsResponse: %s, %s", response.getStatus(), response.getDataAsString());
switch(response.getStatus()) {
case RPCResponse.STATUS_SUCCESS:
break;
case RPCResponse.STATUS_VALIDATION_ERROR:
form.setErrors(response.getErrors(), true);
break;
case RPCResponse.STATUS_LOGIN_REQUIRED:
case RPCResponse.STATUS_LOGIN_INCORRECT:
// ignore, it is handled as HTTP 403 (onUploadError)
case RPCResponse.STATUS_FAILURE:
default:
ErrorHandler.warn(formatStackTrace(response.getDataAsString()));
}
}
private String formatStackTrace(String errMsg) {
return errMsg == null ? "" : errMsg
.replaceAll("\n", "<br>")
.replaceAll("\t", " ")
;
}
@Override
public boolean onFileQueued(FileQueuedEvent fileQueuedEvent) {
form.clearErrors(true);
File file = fileQueuedEvent.getFile();
form.setValue(FIELD_MIMETYPE, file.getType());
form.setValue(FIELD_FILE, file.getName());
return true;
}
@Override
public boolean onFileQueueError(FileQueueErrorEvent fileQueueErrorEvent) {
form.clearValues();
showFormError(fileQueueErrorEvent.getMessage());
return true;
}
}
private Canvas createProgressBar() {
progressbar.setVertical(false);
progressbar.setWidth100();
progressbar.setHeight(24);
progressbar.setBreadth(1);
VLayout vLayout = new VLayout();
vLayout.addMember(progressbar);
return vLayout;
}
private Canvas createButtons() {
btnOk = new IButton(i18nSgwt.dialog_OkButtonTitle(), new com.smartgwt.client.widgets.events.ClickHandler() {
@Override
public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
onUploadClick();
}
});
btnCancel = new IButton(i18nSgwt.dialog_CancelButtonTitle(), new com.smartgwt.client.widgets.events.ClickHandler() {
@Override
public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
closeWindow();
}
});
btnLayout = new HStack(5);
btnLayout.setAutoHeight();
btnLayout.setLayoutTopMargin(20);
btnLayout.setLayoutAlign(Alignment.CENTER);
btnLayout.setMembers(btnOk, btnCancel);
return btnLayout;
}
}