blob: 28dfa6c834a4fb879a4e6609bd39a0477c5d1c60 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.packaging;
import com.intellij.icons.AllIcons;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.webcore.packaging.PackagesNotificationPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.util.*;
/**
* @author vlan
*/
public class PyPackageManagerUI {
private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class);
@Nullable private Listener myListener;
@NotNull private Project myProject;
@NotNull private Sdk mySdk;
public interface Listener {
void started();
void finished(List<PyExternalProcessException> exceptions);
}
public PyPackageManagerUI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) {
myProject = project;
mySdk = sdk;
myListener = listener;
}
public void installManagement() {
ProgressManager.getInstance().run(new InstallManagementTask(myProject, mySdk, myListener));
}
public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) {
ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener));
}
public void uninstall(@NotNull final List<PyPackage> packages) {
if (checkDependents(packages)) {
return;
}
ProgressManager.getInstance().run(new UninstallTask(myProject, mySdk, myListener, packages));
}
private boolean checkDependents(@NotNull final List<PyPackage> packages) {
try {
final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk);
final int[] warning = {0};
if (!dependentPackages.isEmpty()) {
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@Override
public void run() {
if (dependentPackages.size() == 1) {
String message = "You are attempting to uninstall ";
List<String> dep = new ArrayList<String>();
int size = 1;
for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
final Set<PyPackage> value = entry.getValue();
size = value.size();
dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", "));
}
message += StringUtil.join(dep, "\n");
message += size == 1 ? " package" : " packages";
message += "\n\nDo you want to proceed?";
warning[0] = Messages.showYesNoDialog(message, "Warning",
AllIcons.General.BalloonWarning);
}
else {
String message = "You are attempting to uninstall packages which are required for another packages.\n\n";
List<String> dep = new ArrayList<String>();
for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", "));
}
message += StringUtil.join(dep, "\n");
message += "\n\nDo you want to proceed?";
warning[0] = Messages.showYesNoDialog(message, "Warning",
AllIcons.General.BalloonWarning);
}
}
}, ModalityState.current());
}
if (warning[0] != Messages.YES) return true;
}
catch (PyExternalProcessException e) {
LOG.info("Error loading packages dependents: " + e.getMessage(), e);
}
return false;
}
private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk)
throws PyExternalProcessException {
Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>();
for (PyPackage pkg : packages) {
final Set<PyPackage> dependents = PyPackageManager.getInstance(sdk).getDependents(pkg);
if (dependents != null && !dependents.isEmpty()) {
for (PyPackage dependent : dependents) {
if (!packages.contains(dependent)) {
dependentPackages.put(pkg.getName(), dependents);
}
}
}
}
return dependentPackages;
}
private abstract static class PackagingTask extends Task.Backgroundable {
private static final String PACKAGING_GROUP_ID = "Packaging";
@Nullable protected final Listener myListener;
public PackagingTask(@Nullable Project project, @NotNull String title, @Nullable Listener listener) {
super(project, title);
myListener = listener;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
taskStarted(indicator);
taskFinished(runTask(indicator));
}
@NotNull
protected abstract List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator);
@NotNull
protected abstract String getSuccessTitle();
@NotNull
protected abstract String getSuccessDescription();
@NotNull
protected abstract String getFailureTitle();
protected void taskStarted(@NotNull ProgressIndicator indicator) {
indicator.setText(getTitle() + "...");
if (myListener != null) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
myListener.started();
}
});
}
}
protected void taskFinished(@NotNull final List<PyExternalProcessException> exceptions) {
final Ref<Notification> notificationRef = new Ref<Notification>(null);
if (exceptions.isEmpty()) {
notificationRef.set(new Notification(PACKAGING_GROUP_ID, getSuccessTitle(), getSuccessDescription(),
NotificationType.INFORMATION));
}
else {
final String firstLine = getTitle() + ": error occurred.";
final String description = createDescription(exceptions, firstLine);
notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(),
firstLine + " <a href=\"xxx\">Details...</a>",
NotificationType.ERROR,
new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification,
@NotNull HyperlinkEvent event) {
assert myProject != null;
PackagesNotificationPanel.showError(myProject, getFailureTitle(), description);
}
}
));
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (myListener != null) {
myListener.finished(exceptions);
}
final Notification notification = notificationRef.get();
if (notification != null) {
notification.notify(myProject);
}
}
});
}
}
private static class InstallTask extends PackagingTask {
@NotNull protected final Sdk mySdk;
@NotNull private final List<PyRequirement> myRequirements;
@NotNull private final List<String> myExtraArgs;
public InstallTask(@Nullable Project project,
@NotNull Sdk sdk,
@NotNull List<PyRequirement> requirements,
@NotNull List<String> extraArgs,
@Nullable Listener listener) {
super(project, "Installing packages", listener);
mySdk = sdk;
myRequirements = requirements;
myExtraArgs = extraArgs;
}
@NotNull
@Override
protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
final int size = myRequirements.size();
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
for (int i = 0; i < size; i++) {
final PyRequirement requirement = myRequirements.get(i);
indicator.setText(String.format("Installing package '%s'...", requirement));
indicator.setFraction((double)i / size);
try {
manager.install(Arrays.asList(requirement), myExtraArgs);
}
catch (PyExternalProcessException e) {
exceptions.add(e);
}
}
manager.refresh();
return exceptions;
}
@NotNull
@Override
protected String getSuccessTitle() {
return "Packages installed successfully";
}
@NotNull
@Override
protected String getSuccessDescription() {
return "Installed packages: " + PyPackageUtil.requirementsToString(myRequirements);
}
@NotNull
@Override
protected String getFailureTitle() {
return "Install packages failed";
}
}
private static class InstallManagementTask extends InstallTask {
public InstallManagementTask(@Nullable Project project,
@NotNull Sdk sdk,
@Nullable Listener listener) {
super(project, sdk, Collections.<PyRequirement>emptyList(), Collections.<String>emptyList(), listener);
}
@NotNull
@Override
protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
indicator.setText("Installing packaging tools...");
indicator.setIndeterminate(true);
try {
manager.installManagement();
}
catch (PyExternalProcessException e) {
exceptions.add(e);
}
manager.refresh();
return exceptions;
}
@NotNull
@Override
protected String getSuccessDescription() {
return "Installed Python packaging tools";
}
}
private static class UninstallTask extends PackagingTask {
@NotNull private final Sdk mySdk;
@NotNull private final List<PyPackage> myPackages;
public UninstallTask(@Nullable Project project,
@NotNull Sdk sdk,
@Nullable Listener listener,
@NotNull List<PyPackage> packages) {
super(project, "Uninstalling packages", listener);
mySdk = sdk;
myPackages = packages;
}
@NotNull
@Override
protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
try {
manager.uninstall(myPackages);
return Arrays.asList();
}
catch (PyExternalProcessException e) {
return Arrays.asList(e);
}
finally {
manager.refresh();
}
}
@NotNull
@Override
protected String getSuccessTitle() {
return "Packages uninstalled successfully";
}
@NotNull
@Override
protected String getSuccessDescription() {
final String packagesString = StringUtil.join(myPackages, new Function<PyPackage, String>() {
@Override
public String fun(PyPackage pkg) {
return "'" + pkg.getName() + "'";
}
}, ", ");
return "Uninstalled packages: " + packagesString;
}
@NotNull
@Override
protected String getFailureTitle() {
return "Uninstall packages failed";
}
}
public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) {
final StringBuilder b = new StringBuilder();
b.append(firstLine);
b.append("\n\n");
for (PyExternalProcessException exception : exceptions) {
b.append(exception.toString());
b.append("\n");
}
return b.toString();
}
}