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