001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.client;
029
030import org.opencms.ade.upload.client.I_CmsUploadContext;
031import org.opencms.file.CmsResource;
032import org.opencms.gwt.client.CmsCoreProvider;
033import org.opencms.gwt.client.Messages;
034import org.opencms.gwt.client.rpc.CmsRpcAction;
035import org.opencms.gwt.client.ui.CmsErrorDialog;
036import org.opencms.gwt.client.ui.CmsListItemWidget;
037import org.opencms.gwt.client.ui.CmsPopup;
038import org.opencms.gwt.client.ui.CmsPushButton;
039import org.opencms.gwt.client.ui.I_CmsButton;
040import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
041import org.opencms.gwt.client.ui.input.upload.CmsFileInfo;
042import org.opencms.gwt.client.ui.input.upload.CmsFileInput;
043import org.opencms.gwt.client.ui.input.upload.CmsUploadButton;
044import org.opencms.gwt.client.ui.input.upload.CmsUploadProgressInfo;
045import org.opencms.gwt.client.ui.input.upload.CmsUploader;
046import org.opencms.gwt.client.ui.input.upload.I_CmsUploadDialog;
047import org.opencms.gwt.client.ui.replace.CmsReplaceContentWidget;
048import org.opencms.gwt.shared.CmsListInfoBean;
049import org.opencms.gwt.shared.CmsUploadProgessInfo;
050import org.opencms.gwt.shared.I_CmsUploadConstants;
051import org.opencms.gwt.shared.rpc.I_CmsUploadService;
052import org.opencms.gwt.shared.rpc.I_CmsUploadServiceAsync;
053import org.opencms.util.CmsStringUtil;
054
055import java.util.ArrayList;
056import java.util.Collections;
057import java.util.List;
058
059import com.google.gwt.core.client.GWT;
060import com.google.gwt.event.dom.client.ClickEvent;
061import com.google.gwt.event.dom.client.ClickHandler;
062import com.google.gwt.event.logical.shared.CloseHandler;
063import com.google.gwt.event.shared.HandlerRegistration;
064import com.google.gwt.json.client.JSONArray;
065import com.google.gwt.json.client.JSONObject;
066import com.google.gwt.json.client.JSONParser;
067import com.google.gwt.json.client.JSONString;
068import com.google.gwt.json.client.JSONValue;
069import com.google.gwt.user.client.Command;
070import com.google.gwt.user.client.Timer;
071import com.google.gwt.user.client.rpc.ServiceDefTarget;
072import com.google.gwt.user.client.ui.PopupPanel;
073import com.google.gwt.user.client.ui.RootPanel;
074
075/**
076 * The single file upload dialog.<p>
077 */
078public class CmsSingleFileUploadDialog extends CmsPopup implements I_CmsUploadDialog {
079
080    /** The upload context. */
081    protected I_CmsUploadContext m_context;
082
083    /** The main content panel. */
084    protected CmsReplaceContentWidget m_mainPanel;
085
086    /** Signals that the upload dialog was canceled. */
087    private boolean m_canceled;
088
089    /** Flag indicating the client is waiting for a server response. */
090    private boolean m_clientLoading;
091
092    /** The close handler. */
093    private CloseHandler<PopupPanel> m_closeHandler;
094
095    /** The sum of all file sizes. */
096    private long m_contentLength;
097
098    /** The current file input. */
099    private CmsFileInput m_fileInput;
100
101    /** The file widget. */
102    private CmsListItemWidget m_fileWidget;
103
104    /** The action to execute when the upload dialog is finished. */
105    private Runnable m_finishAction;
106
107    /** The dialog handler. */
108    private CmsSingleFileUploadHandler m_handler;
109
110    /** The close handler registration. */
111    private HandlerRegistration m_handlerReg;
112
113    /** The loading timer. */
114    private Timer m_loadingTimer;
115
116    /** The OK button. */
117    private CmsPushButton m_okButton;
118
119    /** The upload progress widget. */
120    private CmsUploadProgressInfo m_progressInfo;
121
122    /** The progress timer. */
123    private Timer m_updateProgressTimer;
124
125    /** The upload button. */
126    private CmsUploadButton m_uploadButton;
127
128    /** The upload service. */
129    private I_CmsUploadServiceAsync m_uploadService;
130
131    /**
132     * Constructor.<p>
133     *
134     * @param handler the dialog handler
135     * @param dialogTitle the dialog title
136     */
137    public CmsSingleFileUploadDialog(CmsSingleFileUploadHandler handler, String dialogTitle) {
138
139        super(dialogTitle);
140        m_handler = handler;
141        setModal(true);
142        setGlassEnabled(true);
143        catchNotifications();
144        // create the main panel
145        m_mainPanel = new CmsReplaceContentWidget();
146        // set the main panel as content of the popup
147        setMainContent(m_mainPanel);
148        addDialogClose(new Command() {
149
150            public void execute() {
151
152                cancelReplace();
153            }
154        });
155
156        CmsPushButton cancelButton = createButton(
157            org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_CANCEL_0));
158        cancelButton.addClickHandler(new ClickHandler() {
159
160            /**
161             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
162             */
163            public void onClick(ClickEvent event) {
164
165                cancelReplace();
166            }
167        });
168        addButton(cancelButton);
169        createButtons();
170    }
171
172    /**
173     * @see org.opencms.gwt.client.ui.CmsPopup#addCloseHandler(com.google.gwt.event.logical.shared.CloseHandler)
174     */
175    @Override
176    public HandlerRegistration addCloseHandler(CloseHandler<PopupPanel> handler) {
177
178        m_closeHandler = handler;
179        m_handlerReg = super.addCloseHandler(handler);
180        return m_handlerReg;
181    }
182
183    /**
184     * Returns the action which should be executed when the upload dialog is finished.<p>
185     *
186     * @return an action to run when the upload dialog is finished
187     */
188    public Runnable getFinishAction() {
189
190        return m_finishAction;
191    }
192
193    /**
194     * @see org.opencms.gwt.client.ui.CmsPopup#hide()
195     */
196    @Override
197    public void hide() {
198
199        if ((m_fileInput != null) && RootPanel.get().getElement().isOrHasChild(m_fileInput.getElement())) {
200            m_fileInput.removeFromParent();
201        }
202        super.hide();
203    }
204
205    /**
206     * Parses the upload response of the server and decides what to do.<p>
207     *
208     * @param results a JSON Object
209     */
210    @SuppressWarnings("null")
211    public void parseResponse(String results) {
212
213        cancelUpdateProgress();
214        stopLoadingAnimation();
215
216        if ((!m_canceled) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(results)) {
217            JSONObject jsonObject = JSONParser.parseStrict(results).isObject();
218            boolean success = jsonObject.get(I_CmsUploadConstants.KEY_SUCCESS).isBoolean().booleanValue();
219            // If the upload is done so fast that we did not receive any progress information, then
220            // the content length is unknown. For that reason take the request size to show how
221            // much bytes were uploaded.
222            double size = jsonObject.get(I_CmsUploadConstants.KEY_REQUEST_SIZE).isNumber().doubleValue();
223            long requestSize = Double.valueOf(size).longValue();
224            if (m_contentLength == 0) {
225                m_contentLength = requestSize;
226            }
227            if (success) {
228                m_mainPanel.displayDialogInfo(Messages.get().key(Messages.GUI_UPLOAD_INFO_FINISHING_0), false);
229                m_progressInfo.finish();
230                JSONValue uploadedFilesVal = jsonObject.get(I_CmsUploadConstants.KEY_UPLOADED_FILE_NAMES);
231                if (uploadedFilesVal != null) {
232                    JSONArray uploadedFilesArray = uploadedFilesVal.isArray();
233                    if (uploadedFilesArray != null) {
234                        List<String> uploadedFiles = new ArrayList<String>();
235                        if (uploadedFilesArray != null) {
236                            for (int i = 0; i < uploadedFilesArray.size(); i++) {
237                                JSONString entry = uploadedFilesArray.get(i).isString();
238                                if (entry != null) {
239                                    uploadedFiles.add(entry.stringValue());
240                                }
241                            }
242                        }
243                        if (m_context != null) {
244                            m_context.onUploadFinished(uploadedFiles);
245                        }
246                    }
247                }
248                closeOnSuccess();
249            } else {
250                String message = jsonObject.get(I_CmsUploadConstants.KEY_MESSAGE).isString().stringValue();
251                String stacktrace = jsonObject.get(I_CmsUploadConstants.KEY_STACKTRACE).isString().stringValue();
252                showErrorReport(message, stacktrace);
253            }
254        }
255    }
256
257    /**
258     * Sets the upload context.<p>
259     *
260     * @param context the new upload context
261     */
262    public void setContext(I_CmsUploadContext context) {
263
264        m_context = context;
265    }
266
267    /**
268     * Sets an action that should be executed if the upload dialog is finished.<p>
269     *
270     * @param action the action to execute when finished
271     */
272    public void setFinishAction(Runnable action) {
273
274        m_finishAction = action;
275    }
276
277    /**
278     * Shows the error report.<p>
279     *
280     * @param message the message to show
281     * @param stacktrace the stacktrace to show
282     */
283    public void showErrorReport(final String message, final String stacktrace) {
284
285        if (!m_canceled) {
286            CmsErrorDialog errDialog = new CmsErrorDialog(message, stacktrace);
287            if (m_handlerReg != null) {
288                m_handlerReg.removeHandler();
289            }
290            if (m_closeHandler != null) {
291                errDialog.addCloseHandler(m_closeHandler);
292            }
293            hide();
294            errDialog.center();
295        }
296    }
297
298    /**
299     * Cancels the replace process.<p>
300     */
301    protected void cancelReplace() {
302
303        m_canceled = true;
304        if (m_progressInfo != null) {
305            cancelUpdateProgress();
306            CmsRpcAction<Boolean> callback = new CmsRpcAction<Boolean>() {
307
308                /**
309                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
310                 */
311                @Override
312                public void execute() {
313
314                    getUploadService().cancelUpload(this);
315                }
316
317                /**
318                 * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
319                 */
320                @Override
321                protected void onResponse(Boolean result) {
322
323                    hide();
324                }
325            };
326            callback.execute();
327        } else {
328            hide();
329        }
330    }
331
332    /**
333     * Cancels the upload progress timer.<p>
334     */
335    protected void cancelUpdateProgress() {
336
337        if (m_updateProgressTimer != null) {
338            m_updateProgressTimer.cancel();
339        }
340    }
341
342    /**
343     * Returns the resource type name for a given filename.<p>
344     *
345     * @param file the file info
346     *
347     * @return the resource type name
348     */
349    protected String getResourceType(CmsFileInfo file) {
350
351        return CmsCoreProvider.get().getResourceType(file);
352    }
353
354    /**
355     * Returns the upload service instance.<p>
356     *
357     * @return the upload service instance
358     */
359    protected I_CmsUploadServiceAsync getUploadService() {
360
361        if (m_uploadService == null) {
362            m_uploadService = GWT.create(I_CmsUploadService.class);
363            String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.upload.CmsUploadService.gwt");
364            ((ServiceDefTarget)m_uploadService).setServiceEntryPoint(serviceUrl);
365        }
366        return m_uploadService;
367    }
368
369    /**
370     * Returns the upload JSP uri.<p>
371     *
372     * @return the upload JSP uri
373     */
374    protected String getUploadUri() {
375
376        return CmsCoreProvider.get().link(I_CmsUploadConstants.UPLOAD_ACTION_JSP_URI);
377    }
378
379    /**
380     * Sets the file input.<p>
381     *
382     * @param fileInput the file input
383     */
384    protected void setFileInput(CmsFileInput fileInput) {
385
386        // only accept file inputs with a single selected file
387        if (fileInput.getFiles().length == 1) {
388            if (m_okButton != null) {
389                m_okButton.enable();
390            }
391            if (m_fileInput != null) {
392                m_fileInput.removeFromParent();
393            }
394            m_fileInput = fileInput;
395
396            RootPanel.get().add(m_fileInput);
397            m_mainPanel.setContainerWidget(createFileWidget(m_fileInput.getFiles()[0]));
398        }
399    }
400
401    /**
402     * Retrieves the progress information from the server.<p>
403     */
404    protected void updateProgress() {
405
406        CmsRpcAction<CmsUploadProgessInfo> callback = new CmsRpcAction<CmsUploadProgessInfo>() {
407
408            /**
409             * @see org.opencms.gwt.client.rpc.CmsRpcAction#execute()
410             */
411            @Override
412            public void execute() {
413
414                getUploadService().getUploadProgressInfo(this);
415            }
416
417            /**
418             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onFailure(java.lang.Throwable)
419             */
420            @Override
421            public void onFailure(Throwable t) {
422
423                super.onFailure(t);
424                cancelUpdateProgress();
425            }
426
427            /**
428             * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
429             */
430            @Override
431            protected void onResponse(CmsUploadProgessInfo result) {
432
433                updateProgressBar(result);
434            }
435        };
436        callback.execute();
437    }
438
439    /**
440     * Updates the progress bar.<p>
441     *
442     * @param info the progress info
443     */
444    protected void updateProgressBar(CmsUploadProgessInfo info) {
445
446        switch (info.getState()) {
447            case notStarted:
448                break;
449            case running:
450                m_progressInfo.setProgress(info);
451                stopLoadingAnimation();
452                break;
453            case finished:
454                m_progressInfo.finish();
455                m_mainPanel.displayDialogInfo(Messages.get().key(Messages.GUI_UPLOAD_INFO_FINISHING_0), false);
456                startLoadingAnimation(Messages.get().key(Messages.GUI_UPLOAD_INFO_CREATING_RESOURCES_0), 1500);
457                break;
458            default:
459                break;
460        }
461    }
462
463    /**
464     * Uploads the selected file.<p>
465     */
466    protected void uploadFile() {
467
468        hideOkAndUploadButtons();
469        CmsUploader uploader = new CmsUploader();
470        CmsFileInfo info = m_fileInput.getFiles()[0];
471        info.setOverrideFileName(
472            CmsStringUtil.joinPaths(m_handler.getTargetFolderPath(), m_handler.getFileName(info.getFileName())));
473        uploader.uploadFiles(
474            CmsCoreProvider.get().link(I_CmsUploadConstants.UPLOAD_ACTION_JSP_URI),
475            CmsResource.getFolderPath(m_handler.getTargetFolderPath()),
476            true,
477            null,
478            Collections.singletonList(info),
479            Collections.<String> emptyList(),
480            false,
481            this);
482        showProgress();
483    }
484
485    /**
486     * Closes the dialog after a delay.<p>
487     */
488    private void closeOnSuccess() {
489
490        Timer closeTimer = new Timer() {
491
492            /**
493             * @see com.google.gwt.user.client.Timer#run()
494             */
495            @Override
496            public void run() {
497
498                CmsSingleFileUploadDialog.this.hide();
499            }
500        };
501        closeTimer.schedule(1000);
502    }
503
504    /**
505     * Creates a dialog text button.<p>
506     *
507     * @param buttonText the button text
508     *
509     * @return the button
510     */
511    private CmsPushButton createButton(String buttonText) {
512
513        CmsPushButton button = new CmsPushButton();
514        button.setTitle(buttonText);
515        button.setText(buttonText);
516        button.setSize(I_CmsButton.Size.medium);
517        button.setUseMinWidth(true);
518        return button;
519    }
520
521    /**
522     * Creates the "OK", the "Cancel" and the "Change file" button.<p>
523     */
524    private void createButtons() {
525
526        m_okButton = createButton(org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_OK_0));
527        m_okButton.addClickHandler(new ClickHandler() {
528
529            /**
530             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
531             */
532            public void onClick(ClickEvent event) {
533
534                uploadFile();
535            }
536        });
537        if (m_fileInput == null) {
538            m_okButton.disable(Messages.get().key(Messages.GUI_REPLACE_NO_FILE_SELECTED_0));
539        }
540        addButton(m_okButton);
541
542        // add a new upload button
543        m_uploadButton = new CmsUploadButton(m_handler);
544        m_uploadButton.addStyleName(I_CmsLayoutBundle.INSTANCE.uploadButton().uploadDialogButton());
545        m_uploadButton.setText(Messages.get().key(Messages.GUI_REPLACE_CHANGE_FILE_0));
546        addButton(m_uploadButton);
547        m_handler.setButton(m_uploadButton);
548    }
549
550    /**
551     * Creates the widget to display the selected file information.<p>
552     *
553     * @param file the file info
554     *
555     * @return the widget
556     */
557    private CmsListItemWidget createFileWidget(CmsFileInfo file) {
558
559        String subTitle;
560        String resourceType = getResourceType(file);
561        if (file.getFileSize() > 0) {
562            subTitle = CmsUploadButton.formatBytes(file.getFileSize()) + " (" + getResourceType(file) + ")";
563        } else {
564            subTitle = resourceType;
565        }
566        CmsListInfoBean infoBean = new CmsListInfoBean(file.getFileName(), subTitle, null);
567        m_fileWidget = new CmsListItemWidget(infoBean);
568        m_fileWidget.setIcon(CmsCoreProvider.get().getResourceTypeIcon(file));
569        return m_fileWidget;
570    }
571
572    /**
573     * Hides the OK and upload button while processing the upload.<p>
574     */
575    private void hideOkAndUploadButtons() {
576
577        m_uploadButton.setVisible(false);
578        m_okButton.setVisible(false);
579    }
580
581    /**
582     * Starts the upload progress bar.<p>
583     */
584    private void showProgress() {
585
586        CmsFileInfo fileInfo = m_fileInput.getFiles()[0];
587        m_progressInfo = new CmsUploadProgressInfo(Collections.singletonList(fileInfo.getFileName()));
588        m_progressInfo.setContentLength(fileInfo.getFileSize());
589        m_mainPanel.setContainerWidget(m_progressInfo);
590        m_updateProgressTimer = new Timer() {
591
592            @Override
593            public void run() {
594
595                updateProgress();
596            }
597        };
598        m_updateProgressTimer.scheduleRepeating(1000);
599
600    }
601
602    /**
603     * Starts the loading animation.<p>
604     *
605     * Used while client is loading files from hard disk into memory.<p>
606     *
607     * @param msg the message that should be displayed below the loading animation (can also be HTML as String)
608     * @param delayMillis the delay to start the animation with
609     */
610    private void startLoadingAnimation(final String msg, int delayMillis) {
611
612        m_loadingTimer = new Timer() {
613
614            @Override
615            public void run() {
616
617                m_mainPanel.showLoadingAnimation(msg);
618            }
619        };
620        if (delayMillis > 0) {
621            m_loadingTimer.schedule(delayMillis);
622        } else {
623            m_loadingTimer.run();
624        }
625    }
626
627    /**
628     * Stops the client loading animation.<p>
629     */
630    private void stopLoadingAnimation() {
631
632        if (m_loadingTimer != null) {
633            m_loadingTimer.cancel();
634        }
635        if (m_clientLoading) {
636            m_mainPanel.removeLoadingAnimation();
637            m_clientLoading = false;
638        }
639    }
640}