/* * 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 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 requirements, @NotNull final List extraArgs) { ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener)); } public void uninstall(@NotNull final List packages) { if (checkDependents(packages)) { return; } ProgressManager.getInstance().run(new UninstallTask(myProject, mySdk, myListener, packages)); } private boolean checkDependents(@NotNull final List packages) { try { final Map> 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 dep = new ArrayList(); int size = 1; for (Map.Entry> entry : dependentPackages.entrySet()) { final Set 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 dep = new ArrayList(); for (Map.Entry> 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> collectDependents(@NotNull final List packages, Sdk sdk) throws PyExternalProcessException { Map> dependentPackages = new HashMap>(); for (PyPackage pkg : packages) { final Set 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 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 exceptions) { final Ref notificationRef = new Ref(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 + " Details...", 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 myRequirements; @NotNull private final List myExtraArgs; public InstallTask(@Nullable Project project, @NotNull Sdk sdk, @NotNull List requirements, @NotNull List extraArgs, @Nullable Listener listener) { super(project, "Installing packages", listener); mySdk = sdk; myRequirements = requirements; myExtraArgs = extraArgs; } @NotNull @Override protected List runTask(@NotNull ProgressIndicator indicator) { final List exceptions = new ArrayList(); 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.emptyList(), Collections.emptyList(), listener); } @NotNull @Override protected List runTask(@NotNull ProgressIndicator indicator) { final List exceptions = new ArrayList(); 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 myPackages; public UninstallTask(@Nullable Project project, @NotNull Sdk sdk, @Nullable Listener listener, @NotNull List packages) { super(project, "Uninstalling packages", listener); mySdk = sdk; myPackages = packages; } @NotNull @Override protected List 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() { @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 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(); } }