diff options
Diffstat (limited to 'python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java')
-rw-r--r-- | python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java new file mode 100644 index 000000000000..28dfa6c834a4 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java @@ -0,0 +1,367 @@ +/* + * 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(); + } +} |