Package org.rstudio.studio.client.application

Source Code of org.rstudio.studio.client.application.HandleUnsavedCommand

/*
* ApplicationQuit.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.application;

import java.util.ArrayList;

import org.rstudio.core.client.Barrier;
import org.rstudio.core.client.Barrier.Token;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.command.CommandBinder;
import org.rstudio.core.client.command.Handler;
import org.rstudio.core.client.events.BarrierReleasedEvent;
import org.rstudio.core.client.events.BarrierReleasedHandler;
import org.rstudio.core.client.files.FileSystemItem;
import org.rstudio.core.client.widget.MessageDialog;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.events.HandleUnsavedChangesEvent;
import org.rstudio.studio.client.application.events.HandleUnsavedChangesHandler;
import org.rstudio.studio.client.application.events.RestartStatusEvent;
import org.rstudio.studio.client.application.events.SaveActionChangedEvent;
import org.rstudio.studio.client.application.events.SaveActionChangedHandler;
import org.rstudio.studio.client.application.events.SuspendAndRestartEvent;
import org.rstudio.studio.client.application.events.SuspendAndRestartHandler;
import org.rstudio.studio.client.application.model.ApplicationServerOperations;
import org.rstudio.studio.client.application.model.SaveAction;
import org.rstudio.studio.client.application.model.SuspendOptions;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.GlobalProgressDelayer;
import org.rstudio.studio.client.common.filetypes.FileIconResources;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.VoidServerRequestCallback;
import org.rstudio.studio.client.workbench.WorkbenchContext;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.events.LastChanceSaveEvent;
import org.rstudio.studio.client.workbench.model.UnsavedChangesTarget;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog;
import org.rstudio.studio.client.workbench.ui.unsaved.UnsavedChangesDialog.Result;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleRestartRCompletedEvent;
import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
import org.rstudio.studio.client.workbench.views.source.SourceShim;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class ApplicationQuit implements SaveActionChangedHandler,
                                        HandleUnsavedChangesHandler,
                                        SuspendAndRestartHandler
{
   public interface Binder extends CommandBinder<Commands, ApplicationQuit> {}
  
   @Inject
   public ApplicationQuit(ApplicationServerOperations server,
                          GlobalDisplay globalDisplay,
                          EventBus eventBus,
                          WorkbenchContext workbenchContext,
                          SourceShim sourceShim,
                          Provider<UIPrefs> pUiPrefs,
                          Commands commands,
                          Binder binder)
   {
      // save references
      server_ = server;
      globalDisplay_ = globalDisplay;
      eventBus_ = eventBus;
      workbenchContext_ = workbenchContext;
      sourceShim_ = sourceShim;
      pUiPrefs_ = pUiPrefs;
     
      // bind to commands
      binder.bind(commands, this);
     
      // subscribe to events
      eventBus.addHandler(SaveActionChangedEvent.TYPE, this);  
      eventBus.addHandler(HandleUnsavedChangesEvent.TYPE, this);
      eventBus.addHandler(SuspendAndRestartEvent.TYPE, this);
   }
  
 
   // notification that we are ready to quit
   public interface QuitContext
   {
      void onReadyToQuit(boolean saveChanges);
   }
  
   public void forceSwitchProject(final String switchToProject)
   {
      ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
                                    sourceShim_.getUnsavedChanges();
      sourceShim_.handleUnsavedChangesBeforeExit(
            unsavedSourceDocs,                                    
            new Command() {
               @Override
               public void execute()
               {
                  performQuit(true, switchToProject);
               }
            });
   }
  
   public void prepareForQuit(final String caption,
                              final QuitContext quitContext)
   {
      if (workbenchContext_.isServerBusy())
      {
         globalDisplay_.showYesNoMessage(
               MessageDialog.QUESTION,
               caption,
               "The R session is currently busy. Are you sure you want to quit?",
               new Operation() {
                  @Override
                  public void execute()
                  {
                     handleUnsavedChanges(caption, quitContext);
                  }},
               true);
      }
      else
      {
         // if we aren't restoring source documents then close them all now
         if (!pUiPrefs_.get().restoreSourceDocuments().getValue())
         {
            sourceShim_.closeAllSourceDocs(caption, new Command() {
               @Override
               public void execute()
               {
                  handleUnsavedChanges(caption, quitContext);
               }
            });
         }
         else
         {
            handleUnsavedChanges(caption, quitContext);
         }
      }
   }
  
   private void handleUnsavedChanges(String caption,
                                     final QuitContext quitContext)
   {  
      // see what the unsaved changes situation is and prompt accordingly
      final int saveAction = saveAction_.getAction();
      ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
                                             sourceShim_.getUnsavedChanges();
     
      // no unsaved changes at all
      if (saveAction != SaveAction.SAVEASK && unsavedSourceDocs.size() == 0)
      {
         quitContext.onReadyToQuit(saveAction == SaveAction.SAVE);
         return;
      }
     
      // just an unsaved environment
      if (unsavedSourceDocs.size() == 0)
      {       
         // confirm quit and do it
         String prompt = "Save workspace image to " +
                         workbenchContext_.getREnvironmentPath() + "?";
         globalDisplay_.showYesNoMessage(
               GlobalDisplay.MSG_QUESTION,
               caption,
               prompt,
               true,
               new Operation() { public void execute()
               {
                  quitContext.onReadyToQuit(true);     
               }},
               new Operation() { public void execute()
               {
                  quitContext.onReadyToQuit(false);
               }},
               new Operation() { public void execute()
               {
               }},
               "Save",
               "Don't Save",
               true);       
      }
     
      // a single unsaved document
      else if (saveAction != SaveAction.SAVEASK &&
               unsavedSourceDocs.size() == 1)
      {
         sourceShim_.saveWithPrompt(
           unsavedSourceDocs.get(0),
           sourceShim_.revertUnsavedChangesBeforeExitCommand(new Command() {
               @Override
               public void execute()
               {
                  quitContext.onReadyToQuit(saveAction == SaveAction.SAVE);
               }}),
           null);
      }
     
      // multiple save targets
      else
      {
         ArrayList<UnsavedChangesTarget> unsaved =
                                      new ArrayList<UnsavedChangesTarget>();
         if (saveAction == SaveAction.SAVEASK)
            unsaved.add(globalEnvTarget_);
         unsaved.addAll(unsavedSourceDocs);
         new UnsavedChangesDialog(
            caption,
            unsaved,
            new OperationWithInput<UnsavedChangesDialog.Result>() {

               @Override
               public void execute(Result result)
               {
                  ArrayList<UnsavedChangesTarget> saveTargets =
                                                result.getSaveTargets();      
                 
                  // remote global env target from list (if specified) and
                  // compute the saveChanges value
                  boolean saveGlobalEnv = saveAction == SaveAction.SAVE;
                  if (saveAction == SaveAction.SAVEASK)
                     saveGlobalEnv = saveTargets.remove(globalEnvTarget_);
                  final boolean saveChanges = saveGlobalEnv;
                 
                  // save specified documents and then quit
                  sourceShim_.handleUnsavedChangesBeforeExit(
                        saveTargets,                                    
                        new Command() {
                           @Override
                           public void execute()
                           {
                              quitContext.onReadyToQuit(saveChanges);
                           }
                        });
               }
              
            },
           
            // no cancel operation
            null
           
            ).showModal();
      }
     
   }
  
   public void performQuit(boolean saveChanges,
                           String switchToProject)
   {
      performQuit(null, saveChanges, switchToProject);
   }
  
   public void performQuit(String progressMessage,
                           boolean saveChanges,
                           String switchToProject)
   {
      performQuit(progressMessage, saveChanges, switchToProject, null);
   }
  
   public void performQuit(String progressMessage,
                           boolean saveChanges,
                           String switchToProject,
                           Command onQuitAcknowledged)
   {
      new QuitCommand(progressMessage,
                      saveChanges,
                      switchToProject,
                      onQuitAcknowledged).execute();
   }
  
   @Override
   public void onSaveActionChanged(SaveActionChangedEvent event)
   {
      saveAction_ = event.getAction();
   }
  
   @Override
   public void onHandleUnsavedChanges(HandleUnsavedChangesEvent event)
   {
      // command which will be used to callback the server
      class HandleUnsavedCommand implements Command
      {
         public HandleUnsavedCommand(boolean handled)
         {
            handled_ = handled;
         }
        
         @Override
         public void execute()
         {
            // this codepath is for when the user quits R using the q()
            // function -- in this case our standard client quit codepath
            // isn't invoked, and as a result the desktop is not notified
            // that there is a pending quit (so thinks R has crashed when
            // the process exits). since this codepath is only for the quit
            // case (and not the restart or restart and reload cases)
            // we can set the pending quit bit here
            if (Desktop.isDesktop())
            {
               Desktop.getFrame().setPendingQuit(
                        DesktopFrame.PENDING_QUIT_AND_EXIT);
            }
           
            server_.handleUnsavedChangesCompleted(
                                          handled_,
                                          new VoidServerRequestCallback())
         }
        
         private final boolean handled_;
      };
     
      // get unsaved source docs
      ArrayList<UnsavedChangesTarget> unsavedSourceDocs =
                                          sourceShim_.getUnsavedChanges();
     
      if (unsavedSourceDocs.size() == 1)
      {
         sourceShim_.saveWithPrompt(
               unsavedSourceDocs.get(0),
               sourceShim_.revertUnsavedChangesBeforeExitCommand(
                                             new HandleUnsavedCommand(true)),
               new HandleUnsavedCommand(false));
      }
      else if (unsavedSourceDocs.size() > 1)
      {
         new UnsavedChangesDialog(
               "Quit R Session",
               unsavedSourceDocs,
               new OperationWithInput<UnsavedChangesDialog.Result>() {
                  @Override
                  public void execute(Result result)
                  {
                     // save specified documents and then quit
                     sourceShim_.handleUnsavedChangesBeforeExit(
                           result.getSaveTargets(),
                           new HandleUnsavedCommand(true));
                  }
                },
                new HandleUnsavedCommand(false)
         ).showModal();
      }
      else
      {
         new HandleUnsavedCommand(true).execute();
      }
   }
     
    
   @Handler
   public void onRestartR()
   {  
      boolean saveChanges = saveAction_.getAction() != SaveAction.NOSAVE;
      eventBus_.fireEvent(new SuspendAndRestartEvent(
                                 SuspendOptions.createSaveMinimal(saveChanges),
                                 null))

   }
  
   @Override
   public void onSuspendAndRestart(final SuspendAndRestartEvent event)
   {
      // set restart pending for desktop
      setPendinqQuit(DesktopFrame.PENDING_QUIT_AND_RESTART);
     
      ProgressIndicator progress = new GlobalProgressDelayer(
                                             globalDisplay_,
                                             200,
                                             "Restarting R...").getIndicator();
                                      
      // perform the suspend and restart
      eventBus_.fireEvent(
                  new RestartStatusEvent(RestartStatusEvent.RESTART_INITIATED));
      server_.suspendForRestart(event.getSuspendOptions(),
                                new VoidServerRequestCallback(progress) {
         @Override
         protected void onSuccess()
         {
            // send pings until the server restarts
            sendPing(event.getAfterRestartCommand(), 200, 25, new Command() {

               @Override
               public void execute()
               {
                  eventBus_.fireEvent(new RestartStatusEvent(
                                    RestartStatusEvent.RESTART_COMPLETED));
                 
               }
              
            });
         }
         @Override
         protected void onFailure()
         {
            eventBus_.fireEvent(
               new RestartStatusEvent(RestartStatusEvent.RESTART_COMPLETED));
           
            setPendinqQuit(DesktopFrame.PENDING_QUIT_NONE);
         }
      });   
     
   }
  
   private void setPendinqQuit(int pendingQuit)
   {
      if (Desktop.isDesktop())
         Desktop.getFrame().setPendingQuit(pendingQuit);
   }
  
   private void sendPing(final String afterRestartCommand,
                         int delayMs,
                         final int maxRetries,
                         final Command onCompleted)
   { 
      Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {

         private int retries_ = 0;
         private boolean pingDelivered_ = false;
         private boolean pingInFlight_ = false;
        
         @Override
         public boolean execute()
         {
            // if we've already delivered the ping or our retry count
            // is exhausted then return false
            if (pingDelivered_ || (++retries_ > maxRetries))
               return false;
           
            if (!pingInFlight_)
            {
               pingInFlight_ = true;
               server_.ping(new VoidServerRequestCallback() {
                  @Override
                  protected void onSuccess()
                  {
                     pingInFlight_ = false;
                    
                     if (!pingDelivered_)
                     {
                        pingDelivered_ = true;
                       
                        // issue after restart command
                        if (!StringUtil.isNullOrEmpty(afterRestartCommand))
                        {
                           eventBus_.fireEvent(
                                 new SendToConsoleEvent(afterRestartCommand,
                                                        true, true));
                        }
                        // otherwise make sure the console knows we
                        // restarted (ensure prompt and set focus)
                        else
                        {
                           eventBus_.fireEvent(
                                          new ConsoleRestartRCompletedEvent());
                        }
                     }
                    
                     if (onCompleted != null)
                        onCompleted.execute();
                  }
                 
                  @Override
                  protected void onFailure()
                  {
                     pingInFlight_ = false;
                    
                     if (onCompleted != null)
                        onCompleted.execute();
                  }
               });
            }
           
            // keep trying until the ping is delivered
            return true;
         }
        
      }, delayMs);
     
     
   }
  
  
   @Handler
   public void onQuitSession()
   {
      prepareForQuit("Quit R Session", new QuitContext() {
         public void onReadyToQuit(boolean saveChanges)
         {
            performQuit(saveChanges, null);
         }  
      });
   }
  
   private UnsavedChangesTarget globalEnvTarget_ = new UnsavedChangesTarget()
   {
      @Override
      public String getId()
      {
         return "F59C8727-3C63-41F4-989C-B1E1D47760E3";
      }

      @Override
      public ImageResource getIcon()
      {
         return FileIconResources.INSTANCE.iconRdata();
      }

      @Override
      public String getTitle()
      {
         return "Workspace image (.RData)";
      }

      @Override
      public String getPath()
      {
         return workbenchContext_.getREnvironmentPath();
      }
     
   };
  
   private String buildSwitchMessage(String switchToProject)
   {
      String msg = !switchToProject.equals("none") ?
        "Switching to project " +
           FileSystemItem.createFile(switchToProject).getParentPathString() :
        "Closing project";
      return msg + "...";
   }
  
   private class QuitCommand implements Command
   {
      public QuitCommand(String progressMessage,
                         boolean saveChanges,
                         String switchToProject,
                         Command onQuitAcknowledged)
      {
         progressMessage_ = progressMessage;
         saveChanges_ = saveChanges;
         switchToProject_ = switchToProject;
         onQuitAcknowledged_ = onQuitAcknowledged;
      }
     
      public void execute()
      {
         // show delayed progress
         String msg = progressMessage_;
         if (msg == null)
         {
            msg = switchToProject_ != null ?
                                    buildSwitchMessage(switchToProject_) :
                                    "Quitting R Session...";
         }
         final GlobalProgressDelayer progress = new GlobalProgressDelayer(
                                                               globalDisplay_,
                                                               250,
                                                               msg);

         // Use a barrier and LastChanceSaveEvent to allow source documents
         // and client state to be synchronized before quitting.
         Barrier barrier = new Barrier();
         barrier.addBarrierReleasedHandler(new BarrierReleasedHandler()
         {
            public void onBarrierReleased(BarrierReleasedEvent event)
            {
               // All last chance save operations have completed (or possibly
               // failed). Now do the real quit.

               // notify the desktop frame that we are about to quit
               if (Desktop.isDesktop())
               {
                  Desktop.getFrame().setPendingQuit(switchToProject_ != null ?
                           DesktopFrame.PENDING_QUIT_RESTART_AND_RELOAD :
                           DesktopFrame.PENDING_QUIT_AND_EXIT);  
               }
              
               server_.quitSession(
                  saveChanges_,
                  switchToProject_,
                  new ServerRequestCallback<Boolean>()
                  {
                     @Override
                     public void onResponseReceived(Boolean response)
                     {
                        if (response)
                        {
                           // clear progress only if we aren't switching projects
                           // (otherwise we want to leave progress up until
                           // the app reloads)
                           if (switchToProject_ == null)
                              progress.dismiss();
                          
                           // fire onQuitAcknowledged
                           if (onQuitAcknowledged_ != null)
                              onQuitAcknowledged_.execute();
                        }
                        else
                        {
                           onFailedToQuit();
                        }
                     }

                     @Override
                     public void onError(ServerError error)
                     {
                        onFailedToQuit();
                     }
                    
                     private void onFailedToQuit()
                     {
                        progress.dismiss();

                        if (Desktop.isDesktop())
                        {
                           Desktop.getFrame().setPendingQuit(
                                         DesktopFrame.PENDING_QUIT_NONE);
                        }
                     }
                  });
            }
         });

         // We acquire a token to make sure that the barrier doesn't fire before
         // all the LastChanceSaveEvent listeners get a chance to acquire their
         // own tokens.
         Token token = barrier.acquire();
         try
         {
            eventBus_.fireEvent(new LastChanceSaveEvent(barrier));
         }
         finally
         {
            token.release();
         }
      }

      private final boolean saveChanges_;
      private final String switchToProject_;
      private final String progressMessage_;
      private final Command onQuitAcknowledged_;

   };

   private final ApplicationServerOperations server_;
   private final GlobalDisplay globalDisplay_;
   private final Provider<UIPrefs> pUiPrefs_;
   private final EventBus eventBus_;
   private final WorkbenchContext workbenchContext_;
   private final SourceShim sourceShim_;
  
   private SaveAction saveAction_ = SaveAction.saveAsk();
}
TOP

Related Classes of org.rstudio.studio.client.application.HandleUnsavedCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.