/*
* DependencyManager.java
*
* Copyright (C) 2009-15 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.common.dependencies;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.rstudio.core.client.CommandWith2Args;
import org.rstudio.core.client.CommandWithArg;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.widget.MessageDialog;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.GlobalDisplay;
import org.rstudio.studio.client.common.GlobalProgressDelayer;
import org.rstudio.studio.client.common.console.ConsoleProcess;
import org.rstudio.studio.client.common.console.ProcessExitEvent;
import org.rstudio.studio.client.common.dependencies.events.InstallShinyEvent;
import org.rstudio.studio.client.common.dependencies.model.Dependency;
import org.rstudio.studio.client.common.dependencies.model.DependencyServerOperations;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.workbench.views.packages.events.PackageStateChangedEvent;
import org.rstudio.studio.client.workbench.views.packages.events.PackageStateChangedHandler;
import org.rstudio.studio.client.workbench.views.vcs.common.ConsoleProgressDialog;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Command;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class DependencyManager implements InstallShinyEvent.Handler,
PackageStateChangedHandler
{
class DependencyRequest
{
DependencyRequest(
String progressCaptionIn,
String userActionIn,
CommandWith2Args<String,Command> userPromptIn,
Dependency[] dependenciesIn,
boolean silentEmbeddedUpdateIn,
CommandWithArg<Boolean> onCompleteIn)
{
progressCaption = progressCaptionIn;
userAction = userActionIn;
userPrompt = userPromptIn;
dependencies = dependenciesIn;
silentEmbeddedUpdate = silentEmbeddedUpdateIn;
onComplete = onCompleteIn;
}
String progressCaption;
String userAction;
CommandWith2Args<String,Command> userPrompt;
Dependency[] dependencies;
boolean silentEmbeddedUpdate;
CommandWithArg<Boolean> onComplete;
}
@Inject
public DependencyManager(GlobalDisplay globalDisplay,
DependencyServerOperations server,
EventBus eventBus)
{
globalDisplay_ = globalDisplay;
server_ = server;
satisfied_ = new ArrayList<Dependency>();
requestQueue_ = new LinkedList<DependencyRequest>();
eventBus.addHandler(InstallShinyEvent.TYPE, this);
eventBus.addHandler(PackageStateChangedEvent.TYPE, this);
}
public void withDependencies(String progressCaption,
CommandWith2Args<String,Command> userPrompt,
Dependency[] dependencies,
boolean silentEmbeddedUpdate,
CommandWithArg<Boolean> onComplete)
{
withDependencies(progressCaption,
null,
userPrompt,
dependencies,
silentEmbeddedUpdate,
onComplete);
}
public void withDependencies(String progressCaption,
String userAction,
Dependency[] dependencies,
boolean silentEmbeddedUpdate,
final CommandWithArg<Boolean> onComplete)
{
withDependencies(progressCaption,
userAction,
null,
dependencies,
silentEmbeddedUpdate,
onComplete);
}
public void withPackrat(String userAction, final Command command)
{
withDependencies(
"Packrat",
userAction,
new Dependency[] {
Dependency.cranPackage("packrat", "0.4.8-1", true)
},
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
});
}
public void withRSConnect(String userAction,
boolean requiresRmarkdown,
CommandWith2Args<String, Command> userPrompt,
final CommandWithArg<Boolean> onCompleted)
{
// build dependency array
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("digest", "0.6"));
deps.add(Dependency.cranPackage("RCurl", "1.95"));
deps.add(Dependency.cranPackage("RJSONIO", "1.0"));
deps.add(Dependency.cranPackage("PKI", "0.1"));
deps.add(Dependency.cranPackage("rstudioapi", "0.5"));
deps.add(Dependency.cranPackage("yaml", "2.1.5"));
if (requiresRmarkdown)
deps.addAll(rmarkdownDependencies());
deps.add(Dependency.cranPackage("packrat", "0.4.8-1", true));
deps.add(Dependency.embeddedPackage("rsconnect"));
withDependencies(
"Publishing",
userAction,
userPrompt,
deps.toArray(new Dependency[deps.size()]),
true, // we want the embedded rsconnect package to be updated if needed
onCompleted
);
}
public void withRMarkdown(String userAction, final Command command)
{
withRMarkdown("R Markdown", userAction, command);
}
public void withRMarkdown(String progressCaption, String userAction,
final Command command)
{
withDependencies(
progressCaption,
userAction,
rmarkdownDependenciesArray(),
true, // we want to update to the embedded version if needed
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
public void withRMarkdown(String progressCaption, String userAction,
final CommandWithArg<Boolean> command)
{
withDependencies(
progressCaption,
userAction,
rmarkdownDependenciesArray(),
true,
command);
}
public static List<Dependency> rmarkdownDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("evaluate", "0.8"));
deps.add(Dependency.cranPackage("digest", "0.6"));
deps.add(Dependency.cranPackage("highr", "0.3"));
deps.add(Dependency.cranPackage("markdown", "0.7"));
deps.add(Dependency.cranPackage("stringr", "0.6"));
deps.add(Dependency.cranPackage("yaml", "2.1.5"));
deps.add(Dependency.cranPackage("Rcpp", "0.11.5"));
deps.add(Dependency.cranPackage("htmltools", "0.3.5"));
deps.add(Dependency.cranPackage("caTools", "1.14"));
deps.add(Dependency.cranPackage("bitops", "1.0-6"));
deps.add(Dependency.cranPackage("knitr", "1.14", true));
deps.add(Dependency.cranPackage("jsonlite", "0.9.19"));
deps.add(Dependency.cranPackage("base64enc", "0.1-3"));
deps.add(Dependency.cranPackage("rprojroot", "1.0"));
deps.add(Dependency.embeddedPackage("rmarkdown"));
return deps;
}
public static Dependency[] rmarkdownDependenciesArray()
{
List<Dependency> deps = rmarkdownDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withShiny(final String userAction, final Command command)
{
// create user prompt command
CommandWith2Args<String, Command> userPrompt =
new CommandWith2Args<String, Command>() {
@Override
public void execute(final String unmetDeps, final Command yesCommand)
{
globalDisplay_.showYesNoMessage(
MessageDialog.QUESTION,
"Install Shiny Package",
userAction + " requires installation of an updated version " +
"of the shiny package.\n\nDo you want to install shiny now?",
new Operation() {
@Override
public void execute()
{
yesCommand.execute();
}
},
true);
}
};
// perform dependency resolution
withDependencies(
"Checking installed packages",
userPrompt,
shinyDependenciesArray(),
true,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
public void withShinyAddins(final Command command)
{
// define dependencies
ArrayList<Dependency> deps = shinyDependencies(); // htmltools version
deps.add(Dependency.cranPackage("miniUI", "0.1.1", true));
deps.add(Dependency.cranPackage("rstudioapi", "0.5", true));
withDependencies(
"Checking installed packages",
"Executing addins",
deps.toArray(new Dependency[deps.size()]),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private Dependency[] shinyDependenciesArray()
{
ArrayList<Dependency> deps = shinyDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
private ArrayList<Dependency> shinyDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("Rcpp", "0.11.5"));
deps.add(Dependency.cranPackage("httpuv", "1.3.3"));
deps.add(Dependency.cranPackage("mime", "0.3"));
deps.add(Dependency.cranPackage("jsonlite", "0.9.19"));
deps.add(Dependency.cranPackage("xtable", "1.7"));
deps.add(Dependency.cranPackage("digest", "0.6"));
deps.add(Dependency.cranPackage("R6", "2.0"));
deps.add(Dependency.cranPackage("sourcetools", "0.1.5"));
deps.add(Dependency.cranPackage("htmltools", "0.3.5"));
deps.add(Dependency.cranPackage("shiny", "0.13", true));
return deps;
}
@Override
public void onInstallShiny(InstallShinyEvent event)
{
withShiny(event.getUserAction(),
new Command() { public void execute() {}});
}
@Override
public void onPackageStateChanged(PackageStateChangedEvent event)
{
// when the package state changes, clear the dependency cache -- this
// is extremely conservative as it's unlikely most (or any) of the
// packages have been invalidated, but it's safe to do so since it'll
// just cause us to hit the server once more to verify
satisfied_.clear();
}
public void withDataImportCSV(String userAction, final Command command)
{
withDependencies(
"Preparing Import from CSV",
userAction,
dataImportCsvDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportCsvDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("readr", "0.2.2"));
deps.add(Dependency.cranPackage("Rcpp", "0.11.5"));
return deps;
}
private Dependency[] dataImportCsvDependenciesArray()
{
ArrayList<Dependency> deps = dataImportCsvDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportSAV(String userAction, final Command command)
{
withDependencies(
"Preparing Import from SPSS, SAS and Stata",
userAction,
dataImportSavDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportSavDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("haven", "0.2.0"));
deps.add(Dependency.cranPackage("Rcpp", "0.11.5"));
return deps;
}
private Dependency[] dataImportSavDependenciesArray()
{
ArrayList<Dependency> deps = dataImportSavDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportXLS(String userAction, final Command command)
{
withDependencies(
"Preparing Import from Excel",
userAction,
dataImportXlsDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportXlsDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("readxl", "0.1.0"));
deps.add(Dependency.cranPackage("Rcpp", "0.11.5"));
return deps;
}
private Dependency[] dataImportXlsDependenciesArray()
{
ArrayList<Dependency> deps = dataImportXlsDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportXML(String userAction, final Command command)
{
withDependencies(
"Preparing Import from XML",
userAction,
dataImportXmlDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportXmlDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("xml2", "0.1.2"));
return deps;
}
private Dependency[] dataImportXmlDependenciesArray()
{
ArrayList<Dependency> deps = dataImportXmlDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportJSON(String userAction, final Command command)
{
withDependencies(
"Preparing Import from JSON",
userAction,
dataImportJsonDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportJsonDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("jsonlite", "0.9.19"));
return deps;
}
private Dependency[] dataImportJsonDependenciesArray()
{
ArrayList<Dependency> deps = dataImportJsonDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportJDBC(String userAction, final Command command)
{
withDependencies(
"Preparing Import from JDBC",
userAction,
dataImportJdbcDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportJdbcDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("RJDBC", "0.2-5"));
deps.add(Dependency.cranPackage("rJava", "0.4-15"));
return deps;
}
private Dependency[] dataImportJdbcDependenciesArray()
{
ArrayList<Dependency> deps = dataImportJdbcDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportODBC(String userAction, final Command command)
{
withDependencies(
"Preparing Import from ODBC",
userAction,
dataImportOdbcDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportOdbcDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("RODBC", "1.3-12"));
return deps;
}
private Dependency[] dataImportOdbcDependenciesArray()
{
ArrayList<Dependency> deps = dataImportOdbcDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withDataImportMongo(String userAction, final Command command)
{
withDependencies(
"Preparing Import from Mongo DB",
userAction,
dataImportMongoDependenciesArray(),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
private ArrayList<Dependency> dataImportMongoDependencies()
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage("mongolite", "0.8"));
deps.add(Dependency.cranPackage("jsonlite", "0.9.19"));
return deps;
}
private Dependency[] dataImportMongoDependenciesArray()
{
ArrayList<Dependency> deps = dataImportMongoDependencies();
return deps.toArray(new Dependency[deps.size()]);
}
public void withProfvis(String userAction, final Command command)
{
withDependencies(
"Preparing Profiler",
userAction,
new Dependency[] {
Dependency.cranPackage("stringr", "0.6"),
Dependency.cranPackage("jsonlite", "0.9.19"),
Dependency.cranPackage("htmltools", "0.3"),
Dependency.cranPackage("yaml", "2.1.5"),
Dependency.cranPackage("htmlwidgets", "0.6", true),
Dependency.cranPackage("profvis", "0.3.2", true)
},
true, // update profvis if needed
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
command.execute();
}
}
);
}
public void withConnectionPackage(String connectionName,
String packageName,
String packageVersion,
final Operation operation)
{
withDependencies(
"Preparing Connection",
connectionName,
connectionPackageDependenciesArray(packageName, packageVersion),
false,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean succeeded)
{
if (succeeded)
operation.execute();
}
}
);
}
private Dependency[] connectionPackageDependenciesArray(String packageName,
String packageVersion)
{
ArrayList<Dependency> deps = new ArrayList<Dependency>();
deps.add(Dependency.cranPackage(packageName, packageVersion));
return deps.toArray(new Dependency[deps.size()]);
}
private void withDependencies(String progressCaption,
final String userAction,
final CommandWith2Args<String,Command> userPrompt,
Dependency[] dependencies,
final boolean silentEmbeddedUpdate,
final CommandWithArg<Boolean> onComplete)
{
// add the request to the queue rather than processing it right away;
// this frees us of the burden of trying to de-dupe requests for the
// same packages which may occur simultaneously (since we also cache
// results, all such duplicate requests will return simultaneously, fed
// by a single RPC)
requestQueue_.add(new DependencyRequest(progressCaption, userAction,
userPrompt, dependencies, silentEmbeddedUpdate,
new CommandWithArg<Boolean>()
{
@Override
public void execute(Boolean arg)
{
// complete the user action, if any
onComplete.execute(arg);
// process the next request in the queue
processingQueue_ = false;
processRequestQueue();
}
}));
processRequestQueue();
}
private void processRequestQueue()
{
if (processingQueue_ == true || requestQueue_.isEmpty())
return;
processingQueue_ = true;
processDependencyRequest(requestQueue_.pop());
}
private void processDependencyRequest(final DependencyRequest req)
{
// convert dependencies to JsArray, excluding satisfied dependencies
final JsArray<Dependency> deps = JsArray.createArray().cast();
for (int i = 0; i < req.dependencies.length; i++)
{
boolean satisfied = false;
for (Dependency d: satisfied_)
{
if (req.dependencies[i].isEqualTo(d))
{
satisfied = true;
break;
}
}
if (!satisfied)
deps.push(req.dependencies[i]);
}
// if no unsatisfied dependencies were found, we're done already
if (deps.length() == 0)
{
req.onComplete.execute(true);
return;
}
// create progress indicator
final ProgressIndicator progress = new GlobalProgressDelayer(
globalDisplay_,
250,
req.progressCaption + "...").getIndicator();
// query for unsatisfied dependencies
server_.unsatisfiedDependencies(
deps, req.silentEmbeddedUpdate,
new ServerRequestCallback<JsArray<Dependency>>() {
@Override
public void onResponseReceived(
final JsArray<Dependency> unsatisfiedDeps)
{
progress.onCompleted();
updateSatisfied(deps, unsatisfiedDeps);
// if we've satisfied all dependencies then execute the command
if (unsatisfiedDeps.length() == 0)
{
req.onComplete.execute(true);
return;
}
// check to see if we can satisfy the version requirement for all
// dependencies
String unsatisfiedVersions = "";
for (int i = 0; i < unsatisfiedDeps.length(); i++)
{
if (!unsatisfiedDeps.get(i).getVersionSatisfied())
{
unsatisfiedVersions += unsatisfiedDeps.get(i).getName() +
" " + unsatisfiedDeps.get(i).getVersion();
String version = unsatisfiedDeps.get(i).getAvailableVersion();
if (version.isEmpty())
unsatisfiedVersions += " is not available\n";
else
unsatisfiedVersions += " is required but " + version +
" is available\n";
}
}
if (!unsatisfiedVersions.isEmpty())
{
// error if we can't satisfy requirements
globalDisplay_.showErrorMessage(
StringUtil.isNullOrEmpty(req.userAction) ?
"Packages Not Found" : req.userAction,
"Required package versions could not be found:\n\n" +
unsatisfiedVersions + "\n" +
"Check that getOption(\"repos\") refers to a CRAN " +
"repository that contains the needed package versions.");
req.onComplete.execute(false);
}
else
{
// otherwise ask the user if they want to install the
// unsatisifed dependencies
final CommandWithArg<Boolean> installCommand =
new CommandWithArg<Boolean>() {
@Override
public void execute(Boolean confirmed)
{
// bail if user didn't confirm
if (!confirmed)
{
req.onComplete.execute(false);
return;
}
// the incoming JsArray from the server may not serialize
// as expected when this code is executed from a satellite
// (see RemoteServer.sendRequestViaMainWorkbench), so we
// clone it before passing to the dependency installer
JsArray<Dependency> newArray = JsArray.createArray().cast();
newArray.setLength(unsatisfiedDeps.length());
for (int i = 0; i < unsatisfiedDeps.length(); i++)
{
newArray.set(i, unsatisfiedDeps.get(i));
}
installDependencies(
newArray,
req.silentEmbeddedUpdate,
req.onComplete);
}
};
if (req.userPrompt != null)
{
req.userPrompt.execute(describeDepPkgs(unsatisfiedDeps),
new Command()
{
@Override
public void execute()
{
installCommand.execute(true);
}
});
}
else
{
confirmPackageInstallation(req.userAction,
unsatisfiedDeps,
installCommand);
}
}
}
@Override
public void onError(ServerError error)
{
progress.onError(error.getUserMessage());
req.onComplete.execute(false);
}
});
}
private void installDependencies(final JsArray<Dependency> dependencies,
final boolean silentEmbeddedUpdate,
final CommandWithArg<Boolean> onComplete)
{
server_.installDependencies(
dependencies,
new ServerRequestCallback<ConsoleProcess>() {
@Override
public void onResponseReceived(ConsoleProcess proc)
{
final ConsoleProgressDialog dialog =
new ConsoleProgressDialog(proc, server_);
dialog.showModal();
proc.addProcessExitHandler(
new ProcessExitEvent.Handler()
{
@Override
public void onProcessExit(ProcessExitEvent event)
{
ifDependenciesSatisifed(dependencies,
silentEmbeddedUpdate,
new CommandWithArg<Boolean>(){
@Override
public void execute(Boolean succeeded)
{
dialog.hide();
onComplete.execute(succeeded);
}
});
}
});
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
globalDisplay_.showErrorMessage(
"Dependency installation failed",
error.getUserMessage());
onComplete.execute(false);
}
});
}
private void ifDependenciesSatisifed(JsArray<Dependency> dependencies,
boolean silentEmbeddedUpdate,
final CommandWithArg<Boolean> onComplete)
{
server_.unsatisfiedDependencies(
dependencies, silentEmbeddedUpdate,
new ServerRequestCallback<JsArray<Dependency>>() {
@Override
public void onResponseReceived(JsArray<Dependency> dependencies)
{
onComplete.execute(dependencies.length() == 0);
}
@Override
public void onError(ServerError error)
{
Debug.logError(error);
globalDisplay_.showErrorMessage(
"Could not determine available packages",
error.getUserMessage());
onComplete.execute(false);
}
});
}
private void confirmPackageInstallation(
String userAction,
final JsArray<Dependency> dependencies,
final CommandWithArg<Boolean> onComplete)
{
String msg = null;
if (dependencies.length() == 1)
{
msg = "requires an updated version of the " +
dependencies.get(0).getName() + " package. " +
"\n\nDo you want to install this package now?";
}
else
{
msg = "requires updated versions of the following packages: " +
describeDepPkgs(dependencies) + ". " +
"\n\nDo you want to install these packages now?";
}
if (userAction != null)
{
globalDisplay_.showYesNoMessage(
MessageDialog.QUESTION,
"Install Required Packages",
userAction + " " + msg,
false,
new Operation() {
@Override
public void execute()
{
onComplete.execute(true);
}
},
new Operation() {
@Override
public void execute()
{
onComplete.execute(false);
}
},
true);
}
else
{
onComplete.execute(false);
}
}
private String describeDepPkgs(JsArray<Dependency> dependencies)
{
ArrayList<String> deps = new ArrayList<String>();
for (int i = 0; i < dependencies.length(); i++)
deps.add(dependencies.get(i).getName());
return StringUtil.join(deps, ", ");
}
public void withUnsatisfiedDependencies(final Dependency dependency,
final ServerRequestCallback<JsArray<Dependency>> requestCallback)
{
// determine if already satisfied
for (Dependency d: satisfied_)
{
if (d.isEqualTo(dependency))
{
JsArray<Dependency> empty = JsArray.createArray().cast();
requestCallback.onResponseReceived(empty);
return;
}
}
List<Dependency> dependencies = new ArrayList<Dependency>();
dependencies.add(dependency);
withUnsatisfiedDependencies(dependencies, requestCallback);
}
private void withUnsatisfiedDependencies(final List<Dependency> dependencies,
final ServerRequestCallback<JsArray<Dependency>> requestCallback)
{
final JsArray<Dependency> jsDependencies =
JsArray.createArray(dependencies.size()).cast();
for (int i = 0; i < dependencies.size(); i++)
jsDependencies.set(i, dependencies.get(i));
server_.unsatisfiedDependencies(
jsDependencies,
false,
new ServerRequestCallback<JsArray<Dependency>>()
{
@Override
public void onResponseReceived(JsArray<Dependency> unsatisfied)
{
updateSatisfied(jsDependencies, unsatisfied);
requestCallback.onResponseReceived(unsatisfied);
}
@Override
public void onError(ServerError error)
{
requestCallback.onError(error);
}
});
}
/**
* Updates the cache of satisfied dependencies.
*
* @param all The dependencies that were requested
* @param unsatisfied The dependencies that were not satisfied
*/
private void updateSatisfied(JsArray<Dependency> all,
JsArray<Dependency> unsatisfied)
{
for (int i = 0; i < all.length(); i++)
{
boolean satisfied = true;
for (int j = 0; j < unsatisfied.length(); j++)
{
if (unsatisfied.get(j).isEqualTo(all.get(i)))
{
satisfied = false;
break;
}
}
if (satisfied)
{
satisfied_.add(all.get(i));
}
}
}
private boolean processingQueue_ = false;
private final LinkedList<DependencyRequest> requestQueue_;
private final GlobalDisplay globalDisplay_;
private final DependencyServerOperations server_;
private final ArrayList<Dependency> satisfied_;
}