Package org.rstudio.studio.client.workbench.views.packages

Source Code of org.rstudio.studio.client.workbench.views.packages.Packages

/*
* Packages.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.workbench.views.packages;

import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.rstudio.core.client.Debug;
import org.rstudio.core.client.JsArrayUtil;
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.files.FileSystemItem;
import org.rstudio.core.client.js.JsObject;
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.core.client.widget.ProgressOperationWithInput;
import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.application.events.SuspendAndRestartEvent;
import org.rstudio.studio.client.application.model.SuspendOptions;
import org.rstudio.studio.client.common.FileDialogs;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror;
import org.rstudio.studio.client.packrat.PackratUtil;
import org.rstudio.studio.client.packrat.model.PackratConflictActions;
import org.rstudio.studio.client.packrat.model.PackratConflictResolution;
import org.rstudio.studio.client.packrat.model.PackratContext;
import org.rstudio.studio.client.packrat.model.PackratPackageAction;
import org.rstudio.studio.client.packrat.model.PackratServerOperations;
import org.rstudio.studio.client.packrat.ui.PackratActionDialog;
import org.rstudio.studio.client.packrat.ui.PackratResolveConflictDialog;
import org.rstudio.studio.client.server.ServerDataSource;
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.WorkbenchView;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.ClientState;
import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
import org.rstudio.studio.client.workbench.model.Session;
import org.rstudio.studio.client.workbench.model.helper.JSObjectStateValue;
import org.rstudio.studio.client.workbench.views.BasePresenter;
import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptEvent;
import org.rstudio.studio.client.workbench.views.console.events.ConsolePromptHandler;
import org.rstudio.studio.client.workbench.views.console.events.SendToConsoleEvent;
import org.rstudio.studio.client.workbench.views.help.events.ShowHelpEvent;
import org.rstudio.studio.client.workbench.views.packages.events.PackageStateChangedEvent;
import org.rstudio.studio.client.workbench.views.packages.events.LoadedPackageUpdatesEvent;
import org.rstudio.studio.client.workbench.views.packages.events.PackageStatusChangedEvent;
import org.rstudio.studio.client.workbench.views.packages.events.PackageStatusChangedHandler;
import org.rstudio.studio.client.workbench.views.packages.events.RaisePackagePaneEvent;
import org.rstudio.studio.client.workbench.views.packages.model.PackageInfo;
import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallContext;
import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallOptions;
import org.rstudio.studio.client.workbench.views.packages.model.PackageInstallRequest;
import org.rstudio.studio.client.workbench.views.packages.model.PackageLibraryUtils;
import org.rstudio.studio.client.workbench.views.packages.model.PackageLibraryUtils.PackageLibraryType;
import org.rstudio.studio.client.workbench.views.packages.model.PackageState;
import org.rstudio.studio.client.workbench.views.packages.model.PackageStatus;
import org.rstudio.studio.client.workbench.views.packages.model.PackageUpdate;
import org.rstudio.studio.client.workbench.views.packages.model.PackagesServerOperations;
import org.rstudio.studio.client.workbench.views.packages.ui.CheckForUpdatesDialog;
import org.rstudio.studio.client.workbench.views.packages.ui.CleanUnusedDialog;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Packages
      extends BasePresenter
      implements PackageStatusChangedHandler,
                 DeferredInitCompletedEvent.Handler,
                 PackagesDisplayObserver
{
   public interface Binder extends CommandBinder<Commands, Packages> {}

   public interface Display extends WorkbenchView
   {
      void setPackageState(PackratContext packratContext,
                           List<PackageInfo> packagesDS);
     
      void installPackage(PackageInstallContext installContext,
                          PackageInstallOptions defaultInstallOptions,
                          PackagesServerOperations server,
                          GlobalDisplay globalDisplay,
                          OperationWithInput<PackageInstallRequest> operation);
     
      void setPackageStatus(PackageStatus status);
 
      void setObserver(PackagesDisplayObserver observer) ;
      void setProgress(boolean showProgress);
     
      void setActions(ArrayList<Action> actions);
   }
  
   @Inject
   public Packages(Display view,
                   final EventBus events,
                   PackagesServerOperations server,
                   PackratServerOperations packratServer,
                   GlobalDisplay globalDisplay,
                   Session session,
                   Binder binder,
                   Commands commands,
                   WorkbenchContext workbenchContext,
                   DefaultCRANMirror defaultCRANMirror,
                   RemoteFileSystemContext fsContext,
                   PackratUtil packratUtil,
                   Provider<FileDialogs> pFileDialogs)
   {
      super(view);
      view_ = view;
      server_ = server;
      packratServer_ = packratServer;
      globalDisplay_ = globalDisplay ;
      view_.setObserver(this) ;
      events_ = events ;
      defaultCRANMirror_ = defaultCRANMirror;
      workbenchContext_ = workbenchContext;
      fsContext_ = fsContext;
      packratUtil_ = packratUtil;
      pFileDialogs_ = pFileDialogs;
      session_ = session;
      binder.bind(commands, this);
     
      events.addHandler(PackageStatusChangedEvent.TYPE, this);
     
      // make the install options persistent
      new JSObjectStateValue("packages-pane", "installOptions", ClientState.PROJECT_PERSISTENT,
            session.getSessionInfo().getClientState(), false)
      {
         @Override
         protected void onInit(JsObject value)
         {
            if (value != null)
               installOptions_ = value.cast();
            lastKnownState_ = installOptions_;
         }

         @Override
         protected JsObject getValue()
         {
            return installOptions_.cast();
         }

         @Override
         protected boolean hasChanged()
         {
            if (!PackageInstallOptions.areEqual(lastKnownState_, installOptions_))
            {
               lastKnownState_ = installOptions_;
               return true;
            }

            return false;
         }

         private PackageInstallOptions lastKnownState_;
      };
     
      updatePackageState(true, false);
     
      // after 2 seconds also add the DeferredInitCompleted handler
      // (we wait because if we don't then on first load in a new
      // session where the packages tab is showing updatePackageState
      // will be called twice)
      new Timer() {
         @Override
         public void run()
         {
            events.addHandler(DeferredInitCompletedEvent.TYPE, Packages.this);
         }
      }.schedule(2000);
   }
  
   void onInstallPackage()
   {
      withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){

         @Override
         public void execute(final PackageInstallContext installContext)
         {
            if (installContext.isDefaultLibraryWriteable())
            {
               continueInstallPackage(installContext);
            }
            else
            {
               globalDisplay_.showYesNoMessage(MessageDialog.QUESTION,
                 "Create Package Library",
                 "Would you like to create a personal library '" +
                 installContext.getDefaultUserLibraryPath() + "' " +
                 "to install packages into?",
                 false,
                 new Operation() // Yes operation
                 {
                    @Override
                    public void execute()
                    {
                       ProgressIndicator indicator =
                             globalDisplay_.getProgressIndicator(
                                                  "Error Creating Library");
                        server_.initDefaultUserLibrary(
                              new VoidServerRequestCallback(indicator) {
                                 @Override
                                 protected void onSuccess()
                                 {
                                    // call this function back recursively
                                    // so we can retrieve the updated
                                    // PackageInstallContext from the server
                                    onInstallPackage();
                                 }
                              })
                     }
                 },
                 new Operation() // No operation
                 {
                     @Override
                     public void execute()
                     {
                        globalDisplay_.showMessage(
                              MessageDialog.WARNING,
                              "Install Packages",
                              "Unable to install packages (default library '" +
                              installContext.getDefaultLibraryPath() + "' is " +
                              "not writeable)");
                       
                    
                 },
                 true);
            }
         }
        
      });
   }
  
   void onRaisePackagePane(RaisePackagePaneEvent event)
   {
      view_.bringToFront();
   }
  
   private void continueInstallPackage(
                           final PackageInstallContext installContext)
   {
      // if CRAN needs to be configured then do it
      if (!installContext.isCRANMirrorConfigured())
      {
         defaultCRANMirror_.configure(new Command() {
            public void execute()
            {
               doInstallPackage(installContext);
            }
         });
      }
      else
      {
         doInstallPackage(installContext);
      }  
   }
 
   private void doInstallPackage(final PackageInstallContext installContext)
   {
      // if install options have not yet initialized the default library
      // path then set it now from the context
      if (StringUtil.isNullOrEmpty(installOptions_.getLibraryPath()))
      {
         installOptions_ = PackageInstallOptions.create(
                                 installOptions_.getInstallFromRepository(),
                                 installContext.getDefaultLibraryPath(),
                                 installOptions_.getInstallDependencies());
      }
     
      view_.installPackage(
         installContext,
         installOptions_,
         server_,
         globalDisplay_,
         new OperationWithInput<PackageInstallRequest>()
         {
            public void execute(PackageInstallRequest request)
            {
               installOptions_ = request.getOptions();
              
               boolean usingDefaultLibrary =
                  request.getOptions().getLibraryPath().equals(
                                       installContext.getDefaultLibraryPath());

               StringBuilder command = new StringBuilder();
               command.append("install.packages(");
              
               List<String> packages = request.getPackages();
               if (packages != null)
               {
                  if (packages.size() > 1)
                     command.append("c(");
                  for (int i=0; i<packages.size(); i++)
                  {
                     if (i > 0)
                        command.append(", ");
                     command.append("\"");
                     command.append(packages.get(i));
                     command.append("\"");
                  }
                  if (packages.size() > 1)
                     command.append(")");
                 
                  // dependencies
                  if (!request.getOptions().getInstallDependencies())
                     command.append(", dependencies = FALSE");
               }
               // must be a local package
               else
               {
                  // get path
                  FileSystemItem localPackage = request.getLocalPackage();
                 
                  // convert to string
                  String path = localPackage.getPath();
                 
                  // append command
                  command.append("\"" + path + "\", repos = NULL");
                 
                  // append type = source if needed
                  if (path.endsWith(".tar.gz"))
                     command.append(", type = \"source\"");
               }
              
               if (!usingDefaultLibrary)
               {
                  command.append(", lib=\"");
                  command.append(request.getOptions().getLibraryPath());
                  command.append("\"");
               }
              
               command.append(")");
               String cmd = command.toString();
               executeWithLoadedPackageCheck(new InstallCommand(packages, cmd));
           }
         });
   }
   
  
   void onUpdatePackages()
   {
      withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){

         @Override
         public void execute(final PackageInstallContext installContext)
         {
            // if there are no writeable library paths then we just
            // short circuit to all packages are up to date message
            if (installContext.getWriteableLibraryPaths().length() == 0)
            {
               globalDisplay_.showMessage(MessageDialog.INFO,
                                          "Check for Updates",
                                          "All packages are up to date.");
              
            }
           
            // if CRAN needs to be configured then do it
            else if (!installContext.isCRANMirrorConfigured())
            {
               defaultCRANMirror_.configure(new Command() {
                  public void execute()
                  {
                     doUpdatePackages(installContext);
                  }
               });
            }
           
            // otherwise we are good to go!
            else
            {
               doUpdatePackages(installContext);
            }   
         }
        
      });
   }
  
   private void doUpdatePackages(final PackageInstallContext installContext)
   {
      new CheckForUpdatesDialog(
         globalDisplay_,
         new ServerDataSource<JsArray<PackageUpdate>>() {
            public void requestData(
               ServerRequestCallback<JsArray<PackageUpdate>> requestCallback)
            {
               server_.checkForPackageUpdates(requestCallback);
            }  
         },
         new OperationWithInput<ArrayList<PackageUpdate>>() {
            @Override
            public void execute(ArrayList<PackageUpdate> updates)
            {
               InstallCommand cmd = buildUpdatePackagesCommand(updates,
                                                               installContext);
               executeWithLoadedPackageCheck(cmd);
           
         },
         new Operation() {
            @Override
            public void execute()
            {
               // cancel emits an empty console input line to clear
               // the busy indicator
               events_.fireEvent(new SendToConsoleEvent("", true));
            }
         }).showModal();
   }
  
  
   private InstallCommand buildUpdatePackagesCommand(
                              ArrayList<PackageUpdate> updates,
                              final PackageInstallContext installContext)
   {
      // split the updates into their respective target libraries
      List<String> packages = new ArrayList<String>();
      LinkedHashMap<String, ArrayList<PackageUpdate>> updatesByLibPath =
         new  LinkedHashMap<String, ArrayList<PackageUpdate>>()
      for (PackageUpdate update : updates)
      {
         // auto-create target list if necessary
         String libPath = update.getLibPath();
         if (!updatesByLibPath.containsKey(libPath))
            updatesByLibPath.put(libPath, new ArrayList<PackageUpdate>());
        
         // insert into list
         updatesByLibPath.get(libPath).add(update);
        
         // track global list of packages
         packages.add(update.getPackageName());
      }
     
      // generate an install packages command for each targeted library
      StringBuilder command = new StringBuilder();
      for (String libPath : updatesByLibPath.keySet())
      {
         if (command.length() > 0)
            command.append("\n");
        
         ArrayList<PackageUpdate> libPathUpdates = updatesByLibPath.get(libPath);
         command.append("install.packages(");
         if (libPathUpdates.size() > 1)
            command.append("c(");
         for (int i=0; i<libPathUpdates.size(); i++)
         {
            PackageUpdate update = libPathUpdates.get(i);
            if (i > 0)
               command.append(", ");
            command.append("\"");
            command.append(update.getPackageName());
            command.append("\"");
         }
         if (libPathUpdates.size() > 1)
            command.append(")");
        
         if (!libPath.equals(installContext.getDefaultLibraryPath()))
         {
            command.append(", lib=\"");
            command.append(libPath);
            command.append("\"");
         }
       
         command.append(")");
        
      }
     
      return new InstallCommand(packages, command.toString());
   }
  
  
   @Handler
   public void onRefreshPackages()
   {
      updatePackageState(true, true);
   }
  
   @Handler
   public void onPackratHelp()
   {
      globalDisplay_.openRStudioLink("packrat", false);
   }
  
   @Handler
   public void onPackratClean()
   {
      new CleanUnusedDialog(
         globalDisplay_,
         new ServerDataSource<JsArray<PackratPackageAction>>()
         {
            @Override
            public void requestData(
                  ServerRequestCallback<JsArray<PackratPackageAction>> requestCallback)
            {
               packratServer_.getPendingActions("clean",
                     session_.getSessionInfo().getActiveProjectDir().getPath(),
                     requestCallback);
            }
         },
         new OperationWithInput<ArrayList<PackratPackageAction>>()
         {
            @Override
            public void execute(ArrayList<PackratPackageAction> input)
            {
               executeRemoveCommand(input);
            }
         },
         new Operation()
         {
            @Override
            public void execute()
            {
               // No work needed here
            }
         }).showModal();
   }
  
   @Handler
   public void onPackratBundle()
   {
      pFileDialogs_.get().saveFile(
         "Export Project Bundle to Gzipped Tarball",
         fsContext_,
         workbenchContext_.getCurrentWorkingDir(),
         ".tar.gz",
         false,
         new ProgressOperationWithInput<FileSystemItem>() {
  
            @Override
            public void execute(FileSystemItem input,
                                ProgressIndicator indicator) {
  
               if (input == null)
                  return;
  
               indicator.onCompleted();
  
               String bundleFile = input.getPath();
               if (bundleFile == null)
                  return;
  
               StringBuilder args = new StringBuilder();
               // We use 'overwrite = TRUE' since the UI dialog will prompt
               // us if we want to overwrite
               args
               .append("file = '")
               .append(bundleFile)
               .append("', overwrite = TRUE")
               ;
  
               packratUtil_.executePackratFunction("bundle", args.toString());
            }

            });
   }
  
   public void removePackage(final PackageInfo packageInfo)
   {
      withPackageInstallContext(new OperationWithInput<PackageInstallContext>(){

         @Override
         public void execute(final PackageInstallContext installContext)
         {
            final boolean usingDefaultLibrary = packageInfo.getLibrary().equals(
                                       installContext.getDefaultLibraryPath());
           
            StringBuilder message = new StringBuilder();
            message.append("Are you sure you wish to permanently uninstall the '");
            message.append(packageInfo.getName() + "' package");
            if (!usingDefaultLibrary)
            {
               message.append(" from library '");
               message.append(packageInfo.getLibrary());
               message.append("'");
            }
            message.append("? This action cannot be undone.");
              
            globalDisplay_.showYesNoMessage(
               MessageDialog.WARNING,
               "Uninstall Package ",
               message.toString(),
               new Operation()
               {
                  @Override
                  public void execute()
                  {    
                     StringBuilder command = new StringBuilder();
                     command.append("remove.packages(\"");
                     command.append(packageInfo.getName());
                     command.append("\"");
                     if (!usingDefaultLibrary)
                     {
                        command.append(", lib=\"");
                        command.append(packageInfo.getLibrary());
                        command.append("\"");
                     }
                     command.append(")");
                     String cmd = command.toString();
                     events_.fireEvent(new SendToConsoleEvent(cmd, true));
                 
               },
               true);
         }
      });
   }
     
   @Override
   public void updatePackageState(boolean showProgress, boolean manualUpdate)
   {
      if (showProgress)
         view_.setProgress(true);
      server_.getPackageState(manualUpdate, new PackageStateUpdater());
   }

   public void loadPackage(final String packageName, final String libName)
   { 
      // check status to make sure the package was unloaded
      checkPackageStatusOnNextConsolePrompt(packageName, libName);
     
      // send the command
      StringBuilder command = new StringBuilder();
      command.append("library(\"");
      command.append(packageName);
      command.append("\"");
      command.append(", lib.loc=\"");
      command.append(libName.replaceAll("\\\\", "\\\\\\\\"));
      command.append("\"");
      command.append(")");
      events_.fireEvent(new SendToConsoleEvent(command.toString(), true));
    
   }

   public void unloadPackage(String packageName, String libName)
   {
      // check status to make sure the package was unloaded
      checkPackageStatusOnNextConsolePrompt(packageName, libName);
     
      StringBuilder command = new StringBuilder();
      command.append("detach(\"package:");
      command.append(packageName);
      command.append("\", unload=TRUE)");
      events_.fireEvent(new SendToConsoleEvent(command.toString(), true));
   }
  
   public void showHelp(PackageInfo packageInfo)
   {
      events_.fireEvent(new ShowHelpEvent(packageInfo.getUrl())) ;
   }
  
   public void onPackageStateChanged(PackageStateChangedEvent event)
   {
      PackageState newState = event.getPackageState();
     
      // if the event contains embedded state, apply it directly; if it doesn't,
      // fetch the new state from the server.
      if (newState != null)
         setPackageState(newState);
      else
         updatePackageState(false, false);
   }
  
   @Override
   public void onDeferredInitCompleted(DeferredInitCompletedEvent event)
   {
      updatePackageState(false, false);
   }
  
   public void onPackageFilterChanged(String filter)
   {
      packageFilter_ = filter.toLowerCase();
      setViewPackageList();
   }

   public void onPackageStatusChanged(PackageStatusChangedEvent event)
   {
      PackageStatus status = event.getPackageStatus();
      view_.setPackageStatus(status);
     
      // also update the list of allPackages_
      for (int i = 0; i<allPackages_.size(); i++)
      {
         PackageInfo packageInfo = allPackages_.get(i);
         if (packageInfo.getName().equals(status.getName()) &&
             packageInfo.getLibrary().equals(status.getLib()))
         {
            allPackages_.set(i, status.isLoaded() ? packageInfo.asLoaded() :
                                                    packageInfo.asUnloaded());
         }
      }
   }
  
   private void setViewPackageList()
   {
      ArrayList<PackageInfo> packages = null;
     
      // apply filter (if any)
      if (packageFilter_.length() > 0)
      {
         packages = new ArrayList<PackageInfo>();
        
         // first do prefix search
         for (PackageInfo pkgInfo : allPackages_)
         {
            if (pkgInfo.getName().toLowerCase().startsWith(packageFilter_))
               packages.add(pkgInfo);
         }
        
         // then do contains search on name & desc
         for (PackageInfo pkgInfo : allPackages_)
         {
            if (pkgInfo.getName().toLowerCase().contains(packageFilter_) ||
                pkgInfo.getDesc().toLowerCase().contains(packageFilter_))
            {
               if (!packages.contains(pkgInfo))
                  packages.add(pkgInfo);
            }
         }

         // sort results by library (to preserve grouping)
         Collections.sort(packages, new Comparator<PackageInfo>()
               {
                  @Override
                  public int compare(PackageInfo o1, PackageInfo o2)
                  {
                     return PackageLibraryUtils.typeOfLibrary(
                                   session_, o1.getLibrary()).compareTo(
                             PackageLibraryUtils.typeOfLibrary(
                                   session_, o2.getLibrary()));
                  }
               });
      }
      else
      {
         packages = allPackages_;
      }
     
      view_.setPackageState(packratContext_, packages);
   }
  
   private void checkPackageStatusOnNextConsolePrompt(
                                         final String packageName,
                                         final String libName)
   {
      // remove any existing handler
      removeConsolePromptHandler();
     
      consolePromptHandlerReg_ = events_.addHandler(ConsolePromptEvent.TYPE,
         new ConsolePromptHandler() {
            @Override
            public void onConsolePrompt(ConsolePromptEvent event)
            { 
               // remove handler so it is only called once
               removeConsolePromptHandler();
              
               // check status and set it
               server_.isPackageLoaded(
                         packageName,
                         libName,
                         new ServerRequestCallback<Boolean>() {
                  @Override
                  public void onResponseReceived(Boolean status)
                  {
                     PackageStatus pkgStatus = PackageStatus.create(packageName,
                                                                    libName,
                                                                    status);
                     view_.setPackageStatus(pkgStatus);
                  }

                  @Override
                  public void onError(ServerError error)
                  {
                     // ignore errors
                  }
               })
            }
         });
   }

   private void removeConsolePromptHandler()
   {
      if (consolePromptHandlerReg_ != null)
      {
         consolePromptHandlerReg_.removeHandler();
         consolePromptHandlerReg_ = null;
      }
   }
  
   private void withPackageInstallContext(
         final OperationWithInput<PackageInstallContext> operation)
   {
      final ProgressIndicator indicator =
         globalDisplay_.getProgressIndicator("Error");
      indicator.onProgress("Retrieving package installation context...");

      server_.getPackageInstallContext(
         new SimpleRequestCallback<PackageInstallContext>() {

            @Override
            public void onResponseReceived(PackageInstallContext context)
            {
               indicator.onCompleted();
               operation.execute(context);
            }

            @Override
            public void onError(ServerError error)
            {
               indicator.onError(error.getUserMessage());
            }          
         });    
   }
  
   public void onLoadedPackageUpdates(LoadedPackageUpdatesEvent event)
   {
      restartForInstallWithConfirmation(event.getInstallCmd())
   }
  
   private class InstallCommand
   {
      public InstallCommand(List<String> packages, String cmd)
      {
         this.packages = packages;
         this.cmd = cmd;
      }
      public final List<String> packages;
      public final String cmd;
   }
  
   private void executeWithLoadedPackageCheck(final InstallCommand command)
   {
      // check if we are potentially going to be overwriting an
      // already installed package. if so then prompt for restart
      if ((command.packages != null))
      {
         server_.loadedPackageUpdatesRequired(
               command.packages,
               new ServerRequestCallback<Boolean>() {

                  @Override
                  public void onResponseReceived(Boolean required)
                  {
                     if (required)
                     {
                        restartForInstallWithConfirmation(command.cmd);
                     }
                     else
                     {
                        executePkgCommand(command.cmd);
                     }
                  }

                  @Override
                  public void onError(ServerError error)
                  {
                     Debug.logError(error);
                     executePkgCommand(command.cmd);
                  }

               });
      }
      else
      {
         executePkgCommand(command.cmd);
      }
   }

   private void executePkgCommand(String cmd)
   {
      events_.fireEvent(new SendToConsoleEvent(cmd, true));
   }
  
   private void restartForInstallWithConfirmation(final String installCmd)
   {
      String msg = "One or more of the packages that will be updated by this " +
                   "installation are currently loaded. Restarting R prior " +
                   "to updating these packages is strongly recommended.\n\n" +
                   "RStudio can restart R and then automatically continue " +
                   "the installation after restarting (all work and " +
                   "data will be preserved during the restart).\n\n" +
                   "Do you want to restart R prior to installing?";
                 
      final boolean haveInstallCmd = installCmd.startsWith("install.packages");
     
      globalDisplay_.showYesNoMessage(
            MessageDialog.WARNING,
            "Updating Loaded Packages",
            msg,
            true,
            new Operation() { public void execute()
            {
               events_.fireEvent(new SuspendAndRestartEvent(
                      SuspendOptions.createSaveAll(true), installCmd))
                 
            }},
            new Operation() { public void execute()
            {
               server_.ignoreNextLoadedPackageCheck(
                                            new VoidServerRequestCallback() {
                  @Override
                  public void onSuccess()
                  {
                     if (haveInstallCmd)
                        executePkgCommand(installCmd);
                  }
               });
            }},
            true);  
   }

   private class PackageStateUpdater extends SimpleRequestCallback<PackageState>
   {
      public PackageStateUpdater()
      {
         super("Error Listing Packages");
      }

      @Override
      public void onError(ServerError error)
      {
         // don't show errors during restart
         if (!workbenchContext_.isRestartInProgress())
            super.onError(error);
        
         view_.setProgress(false);
      }

      @Override
      public void onResponseReceived(PackageState response)
      {
         setPackageState(response);
      }
   };
  
   public static class Action
   {
      public Action(String message, String buttonText, Command onExecute)
      {
         message_ = message;
         buttonText_ = buttonText;
         onExecute_ = onExecute;
      }
     
      public String getMessage()
      {
         return message_;
      }
     
      public String getButtonText()
      {
         return buttonText_;
      }
     
      public Command getOnExecute()
      {
         return onExecute_;
      }
     
      private final String message_;
      private final String buttonText_;
      private final Command onExecute_;
   }
  
   private void confirmPackratActions(JsArray<PackratPackageAction> actions,
                                      String actionTitle,
                                      final String packratFunction)
   {
      new PackratActionDialog(actionTitle, actions,
            new OperationWithInput<Void>()
            {
               @Override
               public void execute(Void input)
               {
                  packratUtil_.executePackratFunction(packratFunction,
                        "prompt = FALSE");
               }
            }).showModal();
   }
  
   private void resolvePackratConflicts(
         JsArray<PackratPackageAction> restoreActions,
         JsArray<PackratPackageAction> snapshotActions)
   {
      new PackratResolveConflictDialog(
            createConflictsFromActions(restoreActions, snapshotActions),
            new OperationWithInput<PackratConflictResolution>()
            {
               @Override
               public void execute(PackratConflictResolution input)
               {
                  if (input == PackratConflictResolution.Library)
                  {
                     packratUtil_.executePackratFunction("restore",
                           "prompt = FALSE");
                  }
                  else if (input == PackratConflictResolution.Snapshot)
                  {
                     packratUtil_.executePackratFunction("snapshot",
                           "prompt = FALSE");
                  }
               }
            }).showModal();
   }
  
   private void setViewActions(final PackageState packageState)
   {
      ArrayList<Action> actions = new ArrayList<Action>();
      // if we have both restore and snapshot actions possible, we need to
      // have the user manually pick one
      if (packageState.getRestoreActions().length() > 0 &&
          packageState.getSnapshotActions().length() > 0)
      {
         ArrayList<PackratPackageAction> allActions =
                 new ArrayList<PackratPackageAction>();
         JsArrayUtil.fillList(packageState.getRestoreActions(), allActions);
         JsArrayUtil.fillList(packageState.getSnapshotActions(), allActions);
         actions.add(new Action(conflictMessageFromActions(allActions),
               "Resolve...",
               new Command()
               {
                  @Override
                  public void execute()
                  {
                     resolvePackratConflicts(
                           packageState.getRestoreActions(),
                           packageState.getSnapshotActions());
                  }
               }));
      }
      else if (packageState.getRestoreActions().length() > 0)
      {
         actions.add(new Action(messageFromActions(
               packageState.getRestoreActions()),
               "Update Library...",
               new Command() {
                  @Override
                  public void execute()
                  {
                     confirmPackratActions(packageState.getRestoreActions(),
                                           "Restore", "restore");
                  }
               }));
      }
      else if (packageState.getSnapshotActions().length() > 0)
      {
         actions.add(new Action(messageFromActions(
               packageState.getSnapshotActions()),
               "Update Packrat...",
               new Command() {
                  @Override
                  public void execute()
                  {
                     confirmPackratActions(packageState.getSnapshotActions(),
                                           "Snapshot", "snapshot");
                  }
               }));
      }
      view_.setActions(actions);
   }
  
   private String conflictMessageFromActions(List<PackratPackageAction> actions)
   {
      // count the unique packages involved
      Set<String> packageNames = new TreeSet<String>();
      for (PackratPackageAction action: actions)
      {
         packageNames.add(action.getPackage());
      }
     
      if (packageNames.size() == 1)
         return "Package '" + actions.get(0).getPackage() + "' out of sync";
      else
         return packageNames.size() + " packages out of sync";
   }
  
   private String messageFromActions(JsArray<PackratPackageAction> actions)
   {
      if (actions.length() == 1)
      {
         // If there's just one action, show it
         PackratPackageAction action = actions.get(0);
         return StringUtil.capitalize(action.getAction()) + " '" +
                action.getPackage() + "'";
      }
      else
      {
         return summarizeActions(actions);
      }
   }
  
   private String summarizeActions(JsArray<PackratPackageAction> actions)
   {
      String summary = "";
      Map<String, Integer> actionCounts = new TreeMap<String, Integer>();
      for (int i = 0; i < actions.length(); i++)
      {
         PackratPackageAction action = actions.get(i);
         String actionName = action.getAction();
         if (actionCounts.containsKey(actionName))
            actionCounts.put(actionName, actionCounts.get(actionName) + 1);
         else
            actionCounts.put(actionName, 1);
      }
      String actionNames[] = actionCounts.keySet().toArray(new String[]{});
      for (int i = 0; i < actionNames.length; i++)
      {
         String actionName = actionNames[i];
         if (i == 0)
            summary += StringUtil.capitalize(actionName);
         else
            summary += actionName;
         summary += " ";
         if (actionCounts.get(actionName).equals(1))
            summary += "1 package";
         else
            summary += actionCounts.get(actionName) + " packages";
         if (i < actionNames.length - 2)
            summary += ", ";
         if (i == actionNames.length - 2)
            summary += ", and ";
      }
      return summary;
   }

   private TreeMap<String, PackratPackageAction> createMapFromActions(
         JsArray<PackratPackageAction> actions)
   {
      TreeMap<String, PackratPackageAction> result =
            new TreeMap<String, PackratPackageAction>();
      for (int i = 0; i < actions.length(); i++)
      {
         result.put(actions.get(i).getPackage(), actions.get(i));
      }
      return result;
   }

   private ArrayList<PackratConflictActions> createConflictsFromActions(
         JsArray<PackratPackageAction> restoreActions,
         JsArray<PackratPackageAction> snapshotActions)
   {
      // build a map of all the package actions
      ArrayList<PackratConflictActions> conflicts =
            new ArrayList<PackratConflictActions>();
      TreeMap<String, PackratPackageAction> restoreMap =
            createMapFromActions(restoreActions);
      TreeMap<String, PackratPackageAction> snapshotMap =
            createMapFromActions(snapshotActions);

      // build a union of all affected package names
      Set<String> packageNames = new TreeSet<String>();
      getPackageNamesFromActions(restoreActions, packageNames);
      getPackageNamesFromActions(snapshotActions, packageNames);
     
      // find the action for each package
      for (String packageName: packageNames)
      {
         conflicts.add(PackratConflictActions.create(
               packageName,
               snapshotMap.containsKey(packageName) ?
                     snapshotMap.get(packageName).getMessage() : "",
               restoreMap.containsKey(packageName) ?
                     restoreMap.get(packageName).getMessage() : ""));
      }

      return conflicts;
   }

   public void executeRemoveCommand(ArrayList<PackratPackageAction> actions)
   {
      String cmd = "remove.packages(";
      if (actions.size() == 1)
         cmd += "\"" + actions.get(0).getPackage() +"\"";
      else
      {
         cmd += "c(";
         for (int i = 0; i < actions.size(); i++)
         {
            cmd += "\"" + actions.get(i).getPackage() + "\"";
            if (i < actions.size() - 1)
               cmd += ", ";
         }
         cmd += ")";
      }
      cmd += ")";
      events_.fireEvent(new SendToConsoleEvent(cmd, true));
   }

   private void setPackageState(PackageState newState)
   {
      // sort the packages
      allPackages_ = new ArrayList<PackageInfo>();
      JsArray<PackageInfo> serverPackages = newState.getPackageList();
      for (int i = 0; i < serverPackages.length(); i++)
         allPackages_.add(serverPackages.get(i));
      Collections.sort(allPackages_, new Comparator<PackageInfo>() {
         public int compare(PackageInfo o1, PackageInfo o2)
         {
            // sort first by library, then by name
            int library =
                  PackageLibraryUtils.typeOfLibrary(
                        session_, o1.getLibrary()).compareTo(
                  PackageLibraryUtils.typeOfLibrary(
                        session_, o2.getLibrary()));
            return library == 0 ?
                  o1.getName().compareToIgnoreCase(o2.getName()) :
                  library;
         }
      });
     
      // mark packages out of sync if they have pending actions, and mark
      // which packages are first in their respective libraries
      // (used later to render headers)
      Set<String> outOfSyncPackages = new TreeSet<String>();
      getPackageNamesFromActions(newState.getRestoreActions(),
                                 outOfSyncPackages);
      getPackageNamesFromActions(newState.getSnapshotActions(),
                                 outOfSyncPackages);
      PackageLibraryType libraryType = PackageLibraryType.None;
      for (PackageInfo pkgInfo: allPackages_)
      {
         if (pkgInfo.getInPackratLibary() &&
             outOfSyncPackages.contains(pkgInfo.getName()))
         {
            pkgInfo.setOutOfSync(true);
         }
         PackageLibraryType pkgLibraryType = PackageLibraryUtils.typeOfLibrary(
               session_, pkgInfo.getLibrary());
         if (pkgLibraryType != libraryType)
         {
            pkgInfo.setFirstInLibrary(true);
            libraryType = pkgLibraryType;
         }
      }
     
      packratContext_ = newState.getPackratContext();
      view_.setProgress(false);
      setViewPackageList();
      setViewActions(newState);
   }
  
   private void getPackageNamesFromActions(
         JsArray<PackratPackageAction> actions,
         Set<String> pkgNames)
   {
      if (actions == null)
         return;

      for (int i = 0; i < actions.length(); i++)
         pkgNames.add(actions.get(i).getPackage());
   }
     
   private final Display view_;
   private final PackagesServerOperations server_;
   private final PackratServerOperations packratServer_;
   private ArrayList<PackageInfo> allPackages_ = new ArrayList<PackageInfo>();
   private PackratContext packratContext_;
   private String packageFilter_ = new String();
   private HandlerRegistration consolePromptHandlerReg_ = null;
   private final EventBus events_ ;
   private final GlobalDisplay globalDisplay_ ;
   private final WorkbenchContext workbenchContext_;
   private final PackratUtil packratUtil_;
   private final RemoteFileSystemContext fsContext_;
   private final Provider<FileDialogs> pFileDialogs_;
   private final DefaultCRANMirror defaultCRANMirror_;
   private final Session session_;
   private PackageInstallOptions installOptions_ =
                                  PackageInstallOptions.create(true, "", true);
}
TOP

Related Classes of org.rstudio.studio.client.workbench.views.packages.Packages

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.