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