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}