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