1 /* 2 * ApplicationQuit.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.studio.client.application; 16 17 import java.util.ArrayList; 18 19 import org.rstudio.core.client.Barrier; 20 import org.rstudio.core.client.Barrier.Token; 21 import org.rstudio.core.client.StringUtil; 22 import org.rstudio.core.client.command.CommandBinder; 23 import org.rstudio.core.client.command.Handler; 24 import org.rstudio.core.client.files.FileSystemItem; 25 import org.rstudio.core.client.widget.MessageDialog; 26 import org.rstudio.core.client.widget.Operation; 27 import org.rstudio.core.client.widget.OperationWithInput; 28 import org.rstudio.studio.client.RStudioGinjector; 29 import org.rstudio.studio.client.application.events.EventBus; 30 import org.rstudio.studio.client.application.events.HandleUnsavedChangesEvent; 31 import org.rstudio.studio.client.application.events.QuitInitiatedEvent; 32 import org.rstudio.studio.client.application.events.RestartStatusEvent; 33 import org.rstudio.studio.client.application.events.SaveActionChangedEvent; 34 import org.rstudio.studio.client.application.events.SuspendAndRestartEvent; 35 import org.rstudio.studio.client.application.model.ApplicationServerOperations; 36 import org.rstudio.studio.client.application.model.RVersionSpec; 37 import org.rstudio.studio.client.application.model.SaveAction; 38 import org.rstudio.studio.client.application.model.SuspendOptions; 39 import org.rstudio.studio.client.application.model.TutorialApiCallContext; 40 import org.rstudio.studio.client.common.GlobalDisplay; 41 import org.rstudio.studio.client.common.GlobalProgressDelayer; 42 import org.rstudio.studio.client.common.TimedProgressIndicator; 43 import org.rstudio.studio.client.common.filetypes.FileIcon; 44 import org.rstudio.studio.client.projects.Projects; 45 import org.rstudio.studio.client.projects.events.OpenProjectNewWindowEvent; 46 import org.rstudio.studio.client.server.ServerError; 47 import org.rstudio.studio.client.server.ServerRequestCallback; 48 import org.rstudio.studio.client.server.VoidServerRequestCallback; 49 import org.rstudio.studio.client.workbench.WorkbenchContext; 50 import org.rstudio.studio.client.workbench.commands.Commands; 51 import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent; 52 import org.rstudio.studio.client.workbench.model.Session; 53 import org.rstudio.studio.client.workbench.model.SessionOpener; 54 import org.rstudio.studio.client.workbench.model.UnsavedChangesItem; 55 import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget; 56 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs; 57 import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog; 58 import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog.Result; 59 import org.rstudio.studio.client.workbench.views.jobs.model.JobManager; 60 import org.rstudio.studio.client.workbench.views.source.Source; 61 import org.rstudio.studio.client.workbench.views.terminal.TerminalHelper; 62 63 import com.google.gwt.core.client.GWT; 64 import com.google.gwt.user.client.Command; 65 import com.google.inject.Inject; 66 import com.google.inject.Provider; 67 import com.google.inject.Singleton; 68 69 @Singleton 70 public class ApplicationQuit implements SaveActionChangedEvent.Handler, 71 HandleUnsavedChangesEvent.Handler, 72 SuspendAndRestartEvent.Handler 73 { 74 public interface Binder extends CommandBinder<Commands, ApplicationQuit> {} 75 76 @Inject ApplicationQuit(ApplicationServerOperations server, GlobalDisplay globalDisplay, EventBus eventBus, WorkbenchContext workbenchContext, Provider<Source> pSource, Provider<UserPrefs> pUiPrefs, Commands commands, Binder binder, TerminalHelper terminalHelper, Provider<JobManager> pJobManager, Provider<SessionOpener> pSessionOpener)77 public ApplicationQuit(ApplicationServerOperations server, 78 GlobalDisplay globalDisplay, 79 EventBus eventBus, 80 WorkbenchContext workbenchContext, 81 Provider<Source> pSource, 82 Provider<UserPrefs> pUiPrefs, 83 Commands commands, 84 Binder binder, 85 TerminalHelper terminalHelper, 86 Provider<JobManager> pJobManager, 87 Provider<SessionOpener> pSessionOpener) 88 { 89 // save references 90 server_ = server; 91 globalDisplay_ = globalDisplay; 92 eventBus_ = eventBus; 93 workbenchContext_ = workbenchContext; 94 pSource_ = pSource; 95 pUserPrefs_ = pUiPrefs; 96 terminalHelper_ = terminalHelper; 97 pJobManager_ = pJobManager; 98 pSessionOpener_ = pSessionOpener; 99 100 // bind to commands 101 binder.bind(commands, this); 102 103 // subscribe to events 104 eventBus.addHandler(SaveActionChangedEvent.TYPE, this); 105 eventBus.addHandler(HandleUnsavedChangesEvent.TYPE, this); 106 eventBus.addHandler(SuspendAndRestartEvent.TYPE, this); 107 } 108 109 110 // notification that we are ready to quit 111 public interface QuitContext 112 { onReadyToQuit(boolean saveChanges)113 void onReadyToQuit(boolean saveChanges); 114 } 115 prepareForQuit(final String caption, final QuitContext quitContext)116 public void prepareForQuit(final String caption, 117 final QuitContext quitContext) 118 { 119 prepareForQuit(caption, true /*allowCancel*/, false /*forceSaveAll*/, quitContext); 120 } 121 prepareForQuit(final String caption, final boolean allowCancel, final boolean forceSaveAll, final QuitContext quitContext)122 public void prepareForQuit(final String caption, 123 final boolean allowCancel, 124 final boolean forceSaveAll, 125 final QuitContext quitContext) 126 { 127 String busyMode = pUserPrefs_.get().busyDetection().getValue(); 128 129 boolean busy = workbenchContext_.isServerBusy() || terminalHelper_.warnBeforeClosing(busyMode); 130 String msg = null; 131 if (busy) 132 { 133 if (workbenchContext_.isServerBusy() && !terminalHelper_.warnBeforeClosing(busyMode)) 134 msg = "The R session is currently busy."; 135 else if (workbenchContext_.isServerBusy() && terminalHelper_.warnBeforeClosing(busyMode)) 136 msg = "The R session and the terminal are currently busy."; 137 else 138 msg = "The terminal is currently busy."; 139 } 140 141 eventBus_.fireEvent(new QuitInitiatedEvent()); 142 143 if (busy && !forceSaveAll) 144 { 145 if (allowCancel) 146 { 147 globalDisplay_.showYesNoMessage( 148 MessageDialog.QUESTION, 149 caption, 150 msg + " Are you sure you want to quit?", 151 () -> handleUnfinishedWork(caption, allowCancel, forceSaveAll, quitContext), 152 true); 153 } 154 else 155 { 156 handleUnfinishedWork(caption, allowCancel, forceSaveAll, quitContext); 157 } 158 } 159 else 160 { 161 // if we aren't restoring source documents then close them all now 162 if (pSource_.get() != null && !pUserPrefs_.get().restoreSourceDocuments().getValue()) 163 { 164 pSource_.get().closeAllSourceDocs(caption, 165 () -> handleUnfinishedWork(caption, allowCancel, forceSaveAll, quitContext), 166 null); 167 } 168 else 169 { 170 handleUnfinishedWork(caption, allowCancel, forceSaveAll, quitContext); 171 } 172 } 173 } 174 handleUnfinishedWork(String caption, boolean allowCancel, boolean forceSaveAll, QuitContext quitContext)175 private void handleUnfinishedWork(String caption, 176 boolean allowCancel, 177 boolean forceSaveAll, 178 QuitContext quitContext) 179 { 180 Command handleUnsaved = () -> { 181 // handle unsaved editor changes 182 handleUnsavedChanges(saveAction_.getAction(), caption, allowCancel, forceSaveAll, 183 pSource_.get(), workbenchContext_, globalEnvTarget_, quitContext); 184 }; 185 186 if (allowCancel) 187 { 188 // check for running jobs 189 pJobManager_.get().promptForTermination((confirmed) -> 190 { 191 if (confirmed) 192 { 193 handleUnsaved.execute(); 194 } 195 }); 196 } 197 else 198 { 199 handleUnsaved.execute(); 200 } 201 } 202 203 204 private static boolean handlingUnsavedChanges_; isHandlingUnsavedChanges()205 public static boolean isHandlingUnsavedChanges() 206 { 207 return handlingUnsavedChanges_; 208 } 209 handleUnsavedChanges(final int saveAction, String caption, boolean allowCancel, boolean forceSaveAll, final Source source, final WorkbenchContext workbenchContext, final UnsavedChangesTarget globalEnvTarget, final QuitContext quitContext)210 public static void handleUnsavedChanges(final int saveAction, 211 String caption, 212 boolean allowCancel, 213 boolean forceSaveAll, 214 final Source source, 215 final WorkbenchContext workbenchContext, 216 final UnsavedChangesTarget globalEnvTarget, 217 final QuitContext quitContext) 218 { 219 // see what the unsaved changes situation is and prompt accordingly 220 ArrayList<UnsavedChangesTarget> unsavedSourceDocs = 221 source.getUnsavedChanges(Source.TYPE_FILE_BACKED); 222 223 // force save all 224 if (forceSaveAll) 225 { 226 // save all unsaved documents and then quit 227 source.handleUnsavedChangesBeforeExit( 228 unsavedSourceDocs, 229 new Command() { 230 @Override 231 public void execute() 232 { 233 boolean saveChanges = saveAction != SaveAction.NOSAVE; 234 quitContext.onReadyToQuit(saveChanges); 235 } 236 }); 237 238 return; 239 } 240 // no unsaved changes at all 241 else if (saveAction != SaveAction.SAVEASK && unsavedSourceDocs.size() == 0) 242 { 243 // define quit operation 244 final Operation quitOperation = new Operation() { public void execute() 245 { 246 quitContext.onReadyToQuit(saveAction == SaveAction.SAVE); 247 }}; 248 249 // if this is a quit session then we always prompt 250 if (ApplicationAction.isQuit()) 251 { 252 RStudioGinjector.INSTANCE.getGlobalDisplay().showYesNoMessage( 253 MessageDialog.QUESTION, 254 caption, 255 "Are you sure you want to quit the R session?", 256 quitOperation, 257 true); 258 } 259 else 260 { 261 quitOperation.execute(); 262 } 263 264 return; 265 } 266 267 // just an unsaved environment 268 if (unsavedSourceDocs.size() == 0 && workbenchContext != null) 269 { 270 // confirm quit and do it 271 String prompt = "Save workspace image to " + 272 workbenchContext.getREnvironmentPath() + "?"; 273 RStudioGinjector.INSTANCE.getGlobalDisplay().showYesNoMessage( 274 GlobalDisplay.MSG_QUESTION, 275 caption, 276 prompt, 277 allowCancel, 278 () -> quitContext.onReadyToQuit(true), 279 () -> quitContext.onReadyToQuit(false), 280 () -> {}, 281 "Save", 282 "Don't Save", 283 true); 284 } 285 286 // a single unsaved document (can be any document in desktop mode, but 287 // must be from the main window in web mode) 288 else if (saveAction != SaveAction.SAVEASK && 289 unsavedSourceDocs.size() == 1 && 290 (Desktop.hasDesktopFrame() || 291 !(unsavedSourceDocs.get(0) instanceof UnsavedChangesItem))) 292 { 293 source.saveWithPrompt( 294 unsavedSourceDocs.get(0), 295 source.revertUnsavedChangesBeforeExitCommand(new Command() { 296 @Override 297 public void execute() 298 { 299 quitContext.onReadyToQuit(saveAction == SaveAction.SAVE); 300 }}), 301 null); 302 } 303 304 // multiple save targets 305 else 306 { 307 ArrayList<UnsavedChangesTarget> unsaved = new ArrayList<>(); 308 if (saveAction == SaveAction.SAVEASK && globalEnvTarget != null) 309 unsaved.add(globalEnvTarget); 310 unsaved.addAll(unsavedSourceDocs); 311 new UnsavedChangesDialog( 312 caption, 313 unsaved, 314 new OperationWithInput<UnsavedChangesDialog.Result>() { 315 316 @Override 317 public void execute(Result result) 318 { 319 ArrayList<UnsavedChangesTarget> saveTargets = 320 result.getSaveTargets(); 321 322 // remote global env target from list (if specified) and 323 // compute the saveChanges value 324 boolean saveGlobalEnv = saveAction == SaveAction.SAVE; 325 if (saveAction == SaveAction.SAVEASK && 326 globalEnvTarget != null) 327 saveGlobalEnv = saveTargets.remove(globalEnvTarget); 328 final boolean saveChanges = saveGlobalEnv; 329 330 // save specified documents and then quit 331 source.handleUnsavedChangesBeforeExit( 332 saveTargets, 333 new Command() { 334 @Override 335 public void execute() 336 { 337 quitContext.onReadyToQuit(saveChanges); 338 } 339 }); 340 } 341 }, 342 343 // no cancel operation 344 null 345 ).showModal(); 346 } 347 } 348 performQuit(TutorialApiCallContext callContext, boolean saveChanges)349 public void performQuit(TutorialApiCallContext callContext, boolean saveChanges) 350 { 351 performQuit(callContext, saveChanges, null, null); 352 } 353 performQuit(TutorialApiCallContext callContext, boolean saveChanges, Command onQuitAcknowledged)354 public void performQuit(TutorialApiCallContext callContext, 355 boolean saveChanges, 356 Command onQuitAcknowledged) 357 { 358 performQuit(callContext, null, saveChanges, null, null, onQuitAcknowledged); 359 } 360 performQuit(TutorialApiCallContext callContext, boolean saveChanges, String switchToProject)361 public void performQuit(TutorialApiCallContext callContext, 362 boolean saveChanges, 363 String switchToProject) 364 { 365 performQuit(callContext, saveChanges, switchToProject, null); 366 } 367 performQuit(TutorialApiCallContext callContext, boolean saveChanges, String switchToProject, RVersionSpec switchToRVersion)368 public void performQuit(TutorialApiCallContext callContext, 369 boolean saveChanges, 370 String switchToProject, 371 RVersionSpec switchToRVersion) 372 { 373 performQuit(callContext, null, saveChanges, switchToProject, switchToRVersion); 374 } 375 performQuit(TutorialApiCallContext callContext, String progressMessage, boolean saveChanges, String switchToProject, RVersionSpec switchToRVersion)376 public void performQuit(TutorialApiCallContext callContext, 377 String progressMessage, 378 boolean saveChanges, 379 String switchToProject, 380 RVersionSpec switchToRVersion) 381 { 382 performQuit(callContext, 383 progressMessage, 384 saveChanges, 385 switchToProject, 386 switchToRVersion, 387 null); 388 } 389 performQuit(TutorialApiCallContext callContext, String progressMessage, boolean saveChanges, String switchToProject, RVersionSpec switchToRVersion, Command onQuitAcknowledged)390 public void performQuit(TutorialApiCallContext callContext, 391 String progressMessage, 392 boolean saveChanges, 393 String switchToProject, 394 RVersionSpec switchToRVersion, 395 Command onQuitAcknowledged) 396 { 397 new QuitCommand(callContext, 398 progressMessage, 399 saveChanges, 400 switchToProject, 401 switchToRVersion, 402 onQuitAcknowledged).execute(); 403 } 404 405 @Override onSaveActionChanged(SaveActionChangedEvent event)406 public void onSaveActionChanged(SaveActionChangedEvent event) 407 { 408 saveAction_ = event.getAction(); 409 } 410 411 @Override onHandleUnsavedChanges(HandleUnsavedChangesEvent event)412 public void onHandleUnsavedChanges(HandleUnsavedChangesEvent event) 413 { 414 // command which will be used to callback the server 415 class HandleUnsavedCommand implements Command 416 { 417 public HandleUnsavedCommand(boolean handled) 418 { 419 handled_ = handled; 420 } 421 422 @Override 423 public void execute() 424 { 425 // this codepath is for when the user quits R using the q() 426 // function -- in this case our standard client quit codepath 427 // isn't invoked, and as a result the desktop is not notified 428 // that there is a pending quit (so thinks R has crashed when 429 // the process exits). since this codepath is only for the quit 430 // case (and not the restart or restart and reload cases) 431 // we can set the pending quit bit here 432 if (Desktop.hasDesktopFrame()) 433 { 434 Desktop.getFrame().setPendingQuit( 435 DesktopFrame.PENDING_QUIT_AND_EXIT); 436 } 437 438 server_.handleUnsavedChangesCompleted( 439 handled_, 440 new VoidServerRequestCallback()); 441 } 442 443 private final boolean handled_; 444 } 445 446 // get unsaved source docs 447 ArrayList<UnsavedChangesTarget> unsavedSourceDocs = 448 pSource_.get().getUnsavedChanges(Source.TYPE_FILE_BACKED); 449 450 if (unsavedSourceDocs.size() == 1) 451 { 452 pSource_.get().saveWithPrompt( 453 unsavedSourceDocs.get(0), 454 pSource_.get().revertUnsavedChangesBeforeExitCommand( 455 new HandleUnsavedCommand(true)), 456 new HandleUnsavedCommand(false)); 457 } 458 else if (unsavedSourceDocs.size() > 1) 459 { 460 new UnsavedChangesDialog( 461 "Quit R Session", 462 unsavedSourceDocs, 463 new OperationWithInput<UnsavedChangesDialog.Result>() { 464 @Override 465 public void execute(Result result) 466 { 467 // save specified documents and then quit 468 pSource_.get().handleUnsavedChangesBeforeExit( 469 result.getSaveTargets(), 470 new HandleUnsavedCommand(true)); 471 } 472 }, 473 new HandleUnsavedCommand(false) 474 ).showModal(); 475 } 476 else 477 { 478 new HandleUnsavedCommand(true).execute(); 479 } 480 } 481 482 483 @Handler onRestartR()484 public void onRestartR() 485 { 486 // check for running jobs 487 pJobManager_.get().promptForTermination((confirmed) -> 488 { 489 if (confirmed) 490 { 491 terminalHelper_.warnBusyTerminalBeforeCommand(() -> 492 { 493 boolean saveChanges = saveAction_.getAction() != SaveAction.NOSAVE; 494 eventBus_.fireEvent(new SuspendAndRestartEvent( 495 SuspendOptions.createSaveMinimal(saveChanges), 496 null)); 497 }, "Restart R", "Terminal jobs will be terminated. Are you sure?", 498 pUserPrefs_.get().busyDetection().getValue()); 499 } 500 }); 501 } 502 503 @Handler onSuspendSession()504 public void onSuspendSession() 505 { 506 server_.suspendSession(true, new VoidServerRequestCallback()); 507 } 508 509 @Override onSuspendAndRestart(final SuspendAndRestartEvent event)510 public void onSuspendAndRestart(final SuspendAndRestartEvent event) 511 { 512 // Ignore nested restarts once restart starts 513 if (suspendingAndRestarting_) return; 514 515 // set restart pending for desktop 516 setPendingQuit(DesktopFrame.PENDING_QUIT_AND_RESTART); 517 518 final TimedProgressIndicator progress = new TimedProgressIndicator( 519 globalDisplay_.getProgressIndicator("Error")); 520 progress.onTimedProgress("Restarting R...", 1000); 521 522 final Operation onRestartComplete = () -> { 523 suspendingAndRestarting_ = false; 524 progress.onCompleted(); 525 eventBus_.fireEvent(new RestartStatusEvent(RestartStatusEvent.RESTART_COMPLETED)); 526 }; 527 528 // perform the suspend and restart 529 suspendingAndRestarting_ = true; 530 eventBus_.fireEvent(new RestartStatusEvent(RestartStatusEvent.RESTART_INITIATED)); 531 pSessionOpener_.get().suspendForRestart( 532 event.getAfterRestartCommand(), 533 event.getSuspendOptions(), 534 () -> { // success 535 onRestartComplete.execute(); 536 }, () -> { // failure 537 onRestartComplete.execute(); 538 setPendingQuit(DesktopFrame.PENDING_QUIT_NONE); 539 }); 540 } 541 setPendingQuit(int pendingQuit)542 private void setPendingQuit(int pendingQuit) 543 { 544 if (Desktop.hasDesktopFrame()) 545 Desktop.getFrame().setPendingQuit(pendingQuit); 546 } 547 548 @Handler onQuitSession()549 public void onQuitSession() 550 { 551 prepareForQuit("Quit R Session", (boolean saveChanges) -> performQuit(null, saveChanges)); 552 } 553 554 @Handler onForceQuitSession()555 public void onForceQuitSession() 556 { 557 prepareForQuit("Quit R Session", false /*allowCancel*/, false /*forceSaveChanges*/, 558 (boolean saveChanges) -> performQuit(null, saveChanges)); 559 } 560 doRestart(Session session)561 public void doRestart(Session session) 562 { 563 prepareForQuit( 564 "Restarting RStudio", 565 saveChanges -> { 566 String project = session.getSessionInfo().getActiveProjectFile(); 567 if (project == null) 568 project = Projects.NONE; 569 570 final String finalProject = project; 571 performQuit(null, saveChanges, () -> { 572 eventBus_.fireEvent(new OpenProjectNewWindowEvent(finalProject, null)); 573 }); 574 }); 575 } 576 577 private UnsavedChangesTarget globalEnvTarget_ = new UnsavedChangesTarget() 578 { 579 @Override 580 public String getId() 581 { 582 return "F59C8727-3C63-41F4-989C-B1E1D47760E3"; 583 } 584 585 @Override 586 public FileIcon getIcon() 587 { 588 return FileIcon.RDATA_ICON; 589 } 590 591 @Override 592 public String getTitle() 593 { 594 return "Workspace image (.RData)"; 595 } 596 597 @Override 598 public String getPath() 599 { 600 return workbenchContext_.getREnvironmentPath(); 601 } 602 603 }; 604 buildSwitchMessage(String switchToProject)605 private String buildSwitchMessage(String switchToProject) 606 { 607 String msg = switchToProject != "none" ? 608 "Switching to project " + 609 FileSystemItem.createFile(switchToProject).getParentPathString() : 610 "Closing project"; 611 return msg + "..."; 612 } 613 614 private class QuitCommand implements Command 615 { QuitCommand(TutorialApiCallContext callContext, String progressMessage, boolean saveChanges, String switchToProject, RVersionSpec switchToRVersion, Command onQuitAcknowledged)616 public QuitCommand(TutorialApiCallContext callContext, 617 String progressMessage, 618 boolean saveChanges, 619 String switchToProject, 620 RVersionSpec switchToRVersion, 621 Command onQuitAcknowledged) 622 { 623 callContext_ = callContext; 624 progressMessage_ = progressMessage; 625 saveChanges_ = saveChanges; 626 switchToProject_ = switchToProject; 627 switchToRVersion_ = switchToRVersion; 628 onQuitAcknowledged_ = onQuitAcknowledged; 629 } 630 execute()631 public void execute() 632 { 633 // show delayed progress 634 String msg = progressMessage_; 635 if (msg == null) 636 { 637 msg = switchToProject_ != null ? 638 buildSwitchMessage(switchToProject_) : 639 "Quitting R Session..."; 640 } 641 final GlobalProgressDelayer progress = new GlobalProgressDelayer( 642 globalDisplay_, 643 250, 644 msg); 645 646 // Use a barrier and LastChanceSaveEvent to allow source documents 647 // and client state to be synchronized before quitting. 648 Barrier barrier = new Barrier(); 649 barrier.addBarrierReleasedHandler(releasedEvent -> 650 { 651 // All last chance save operations have completed (or possibly 652 // failed). Now do the real quit. 653 654 // notify the desktop frame that we are about to quit 655 String switchToProject = StringUtil.create(switchToProject_); 656 if (Desktop.hasDesktopFrame()) 657 { 658 Desktop.getFrame().setPendingQuit(switchToProject_ != null ? 659 DesktopFrame.PENDING_QUIT_RESTART_AND_RELOAD : 660 DesktopFrame.PENDING_QUIT_AND_EXIT); 661 } 662 663 server_.quitSession( 664 saveChanges_, 665 switchToProject, 666 switchToRVersion_, 667 GWT.getHostPageBaseURL(), 668 new ServerRequestCallback<Boolean>() 669 { 670 @Override 671 public void onResponseReceived(Boolean response) 672 { 673 if (response) 674 { 675 // clear progress only if we aren't switching projects 676 // (otherwise we want to leave progress up until 677 // the app reloads) 678 if (switchToProject_ == null) 679 progress.dismiss(); 680 681 if (callContext_ != null) 682 { 683 eventBus_.fireEvent(new ApplicationTutorialEvent( 684 ApplicationTutorialEvent.API_SUCCESS, callContext_)); 685 } 686 687 // fire onQuitAcknowledged 688 if (onQuitAcknowledged_ != null) 689 onQuitAcknowledged_.execute(); 690 } 691 else 692 { 693 onFailedToQuit("server quitSession responded false"); 694 } 695 } 696 697 @Override 698 public void onError(ServerError error) 699 { 700 onFailedToQuit(error.getMessage()); 701 } 702 703 private void onFailedToQuit(String message) 704 { 705 progress.dismiss(); 706 707 if (callContext_ != null) 708 { 709 eventBus_.fireEvent(new ApplicationTutorialEvent( 710 ApplicationTutorialEvent.API_ERROR, 711 message, 712 callContext_)); 713 } 714 if (Desktop.hasDesktopFrame()) 715 { 716 Desktop.getFrame().setPendingQuit( 717 DesktopFrame.PENDING_QUIT_NONE); 718 } 719 } 720 }); 721 }); 722 723 // We acquire a token to make sure that the barrier doesn't fire before 724 // all the LastChanceSaveEvent listeners get a chance to acquire their 725 // own tokens. 726 Token token = barrier.acquire(); 727 try 728 { 729 eventBus_.fireEvent(new LastChanceSaveEvent(barrier)); 730 } 731 finally 732 { 733 token.release(); 734 } 735 } 736 737 private final TutorialApiCallContext callContext_; 738 private final boolean saveChanges_; 739 private final String switchToProject_; 740 private final RVersionSpec switchToRVersion_; 741 private final String progressMessage_; 742 private final Command onQuitAcknowledged_; 743 } 744 745 private SaveAction saveAction_ = SaveAction.saveAsk(); 746 private boolean suspendingAndRestarting_ = false; 747 748 // injected 749 private final ApplicationServerOperations server_; 750 private final GlobalDisplay globalDisplay_; 751 private final Provider<UserPrefs> pUserPrefs_; 752 private final EventBus eventBus_; 753 private final WorkbenchContext workbenchContext_; 754 private final Provider<Source> pSource_; 755 private final TerminalHelper terminalHelper_; 756 private final Provider<JobManager> pJobManager_; 757 private final Provider<SessionOpener> pSessionOpener_; 758 } 759