1 package com.intellij.webcore.packaging;
3 import com.google.common.collect.Lists;
4 import com.intellij.icons.AllIcons;
5 import com.intellij.ide.ActivityTracker;
6 import com.intellij.openapi.actionSystem.AnActionEvent;
7 import com.intellij.openapi.application.Application;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.application.ModalityState;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.projectRoots.Sdk;
12 import com.intellij.openapi.ui.Messages;
13 import com.intellij.openapi.util.text.StringUtil;
14 import com.intellij.ui.*;
15 import com.intellij.ui.table.JBTable;
16 import com.intellij.util.CatchingConsumer;
17 import com.intellij.util.Consumer;
18 import com.intellij.util.IconUtil;
19 import com.intellij.util.ObjectUtils;
20 import com.intellij.util.containers.ContainerUtil;
21 import com.intellij.util.containers.HashSet;
22 import com.intellij.util.ui.StatusText;
23 import com.intellij.util.ui.UIUtil;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
28 import javax.swing.event.ListSelectionEvent;
29 import javax.swing.event.ListSelectionListener;
30 import javax.swing.table.DefaultTableCellRenderer;
31 import javax.swing.table.DefaultTableModel;
32 import javax.swing.table.TableCellRenderer;
34 import java.awt.event.MouseEvent;
35 import java.io.IOException;
37 import java.util.List;
38 import java.util.concurrent.atomic.AtomicInteger;
40 public class InstalledPackagesPanel extends JPanel {
41 private final AnActionButton myUpgradeButton;
42 protected final AnActionButton myInstallButton;
43 private final AnActionButton myUninstallButton;
45 protected final JBTable myPackagesTable;
46 private DefaultTableModel myPackagesTableModel;
47 // can be accessed from any thread
48 protected volatile PackageManagementService myPackageManagementService;
49 protected final Project myProject;
50 protected final PackagesNotificationPanel myNotificationArea;
51 protected final List<Consumer<Sdk>> myPathChangedListeners = ContainerUtil.createLockFreeCopyOnWriteList();
52 private final Set<String> myCurrentlyInstalling = ContainerUtil.newHashSet();
53 private final Set<InstalledPackage> myWaitingToUpgrade = ContainerUtil.newHashSet();
55 public InstalledPackagesPanel(Project project, PackagesNotificationPanel area) {
56 super(new BorderLayout());
58 myNotificationArea = area;
60 myPackagesTableModel = new DefaultTableModel(new String[]{"Package", "Version", "Latest"}, 0) {
62 public boolean isCellEditable(int i, int i1) {
66 final TableCellRenderer tableCellRenderer = new MyTableCellRenderer();
67 myPackagesTable = new JBTable(myPackagesTableModel) {
69 public TableCellRenderer getCellRenderer(int row, int column) {
70 return tableCellRenderer;
73 // Defence from javax.swing.JTable.initializeLocalVars:
74 // setPreferredScrollableViewportSize(new Dimension(450, 400));
75 myPackagesTable.setPreferredScrollableViewportSize(null);
76 myPackagesTable.setStriped(true);
77 myPackagesTable.getTableHeader().setReorderingAllowed(false);
79 myUpgradeButton = new AnActionButton("Upgrade", IconUtil.getMoveUpIcon()) {
81 public void actionPerformed(@NotNull AnActionEvent e) {
85 myInstallButton = new AnActionButton("Install", IconUtil.getAddIcon()) {
87 public void actionPerformed(@NotNull AnActionEvent e) {
88 if (myPackageManagementService != null) {
89 ManagePackagesDialog dialog = createManagePackagesDialog();
94 myUninstallButton = new AnActionButton("Uninstall", IconUtil.getRemoveIcon()) {
96 public void actionPerformed(@NotNull AnActionEvent e) {
100 ToolbarDecorator decorator =
101 ToolbarDecorator.createDecorator(myPackagesTable).disableUpDownActions().disableAddAction().disableRemoveAction()
102 .addExtraAction(myInstallButton)
103 .addExtraAction(myUninstallButton)
104 .addExtraAction(myUpgradeButton);
106 add(decorator.createPanel());
107 myInstallButton.setEnabled(false);
108 myUninstallButton.setEnabled(false);
109 myUpgradeButton.setEnabled(false);
111 myPackagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
113 public void valueChanged(ListSelectionEvent event) {
114 updateUninstallUpgrade();
118 new DoubleClickListener() {
120 protected boolean onDoubleClick(MouseEvent e) {
121 if (myPackageManagementService != null && myInstallButton.isEnabled()) {
122 ManagePackagesDialog dialog = createManagePackagesDialog();
123 Point p = e.getPoint();
124 int row = myPackagesTable.rowAtPoint(p);
125 int column = myPackagesTable.columnAtPoint(p);
126 if (row >= 0 && column >= 0) {
127 Object pkg = myPackagesTable.getValueAt(row, 0);
128 if (pkg instanceof InstalledPackage) {
129 dialog.selectPackage((InstalledPackage) pkg);
137 }.installOn(myPackagesTable);
141 protected ManagePackagesDialog createManagePackagesDialog() {
142 return new ManagePackagesDialog(myProject,
143 myPackageManagementService,
144 new PackageManagementService.Listener() {
146 public void operationStarted(String packageName) {
147 myPackagesTable.setPaintBusy(true);
151 public void operationFinished(String packageName,
152 @Nullable PackageManagementService.ErrorDescription errorDescription) {
153 myNotificationArea.showResult(packageName, errorDescription);
154 myPackagesTable.clearSelection();
155 doUpdatePackages(myPackageManagementService);
160 public void addPathChangedListener(Consumer<Sdk> consumer) {
161 myPathChangedListeners.add(consumer);
164 private void upgradeAction() {
165 final int[] rows = myPackagesTable.getSelectedRows();
166 if (myPackageManagementService != null) {
167 final Set<String> upgradedPackages = new HashSet<String>();
168 final Set<String> packagesShouldBePostponed = getPackagesToPostpone();
169 for (int row : rows) {
170 final Object packageObj = myPackagesTableModel.getValueAt(row, 0);
171 if (packageObj instanceof InstalledPackage) {
172 InstalledPackage pkg = (InstalledPackage)packageObj;
173 final String packageName = pkg.getName();
174 final String currentVersion = pkg.getVersion();
175 final String availableVersion = (String)myPackagesTableModel.getValueAt(row, 2);
177 if (packagesShouldBePostponed.contains(packageName)) {
178 myWaitingToUpgrade.add((InstalledPackage)packageObj);
180 else if (isUpdateAvailable(currentVersion, availableVersion)) {
181 upgradePackage(pkg, availableVersion);
182 upgradedPackages.add(packageName);
187 if (myCurrentlyInstalling.isEmpty() && upgradedPackages.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
188 upgradePostponedPackages();
193 private void upgradePostponedPackages() {
194 final Iterator<InstalledPackage> iterator = myWaitingToUpgrade.iterator();
195 final InstalledPackage toUpgrade = iterator.next();
197 upgradePackage(toUpgrade, toUpgrade.getVersion());
200 protected Set<String> getPackagesToPostpone() {
201 return Collections.emptySet();
204 private void upgradePackage(@NotNull final InstalledPackage pkg, @Nullable final String toVersion) {
205 final PackageManagementService selPackageManagementService = myPackageManagementService;
206 myPackageManagementService.fetchPackageVersions(pkg.getName(), new CatchingConsumer<List<String>, Exception>() {
208 public void consume(List<String> releases) {
209 if (!releases.isEmpty() && !isUpdateAvailable(pkg.getVersion(), releases.get(0))) {
213 ApplicationManager.getApplication().invokeLater(new Runnable() {
216 final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
218 public void operationStarted(final String packageName) {
219 UIUtil.invokeLaterIfNeeded(new Runnable() {
222 myPackagesTable.setPaintBusy(true);
223 myCurrentlyInstalling.add(packageName);
229 public void operationFinished(final String packageName,
230 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
231 UIUtil.invokeLaterIfNeeded(new Runnable() {
234 myPackagesTable.clearSelection();
235 updatePackages(selPackageManagementService);
236 myPackagesTable.setPaintBusy(false);
237 myCurrentlyInstalling.remove(packageName);
238 if (errorDescription == null) {
239 myNotificationArea.showSuccess("Package " + packageName + " successfully upgraded");
242 myNotificationArea.showError("Upgrade packages failed. <a href=\"xxx\">Details...</a>", "Upgrade Packages Failed",
246 if (myCurrentlyInstalling.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
247 upgradePostponedPackages();
253 PackageManagementServiceEx serviceEx = getServiceEx();
254 if (serviceEx != null) {
255 serviceEx.updatePackage(pkg, toVersion, listener);
258 myPackageManagementService.installPackage(new RepoPackage(pkg.getName(), null /* TODO? */), null, true, null, listener, false);
260 myUpgradeButton.setEnabled(false);
262 }, ModalityState.any());
266 public void consume(Exception e) {
267 ApplicationManager.getApplication().invokeLater(new Runnable() {
270 Messages.showErrorDialog("Error occurred. Please, check your internet connection.",
271 "Upgrade Package Failed.");
273 }, ModalityState.any());
279 private PackageManagementServiceEx getServiceEx() {
280 return ObjectUtils.tryCast(myPackageManagementService, PackageManagementServiceEx.class);
283 private void updateUninstallUpgrade() {
284 final int[] selected = myPackagesTable.getSelectedRows();
285 boolean upgradeAvailable = false;
286 boolean canUninstall = selected.length != 0;
287 boolean canInstall = true;
288 boolean canUpgrade = true;
289 if (myPackageManagementService != null && selected.length != 0) {
290 for (int i = 0; i != selected.length; ++i) {
291 final int index = selected[i];
292 if (index >= myPackagesTable.getRowCount()) continue;
293 final Object value = myPackagesTable.getValueAt(index, 0);
294 if (value instanceof InstalledPackage) {
295 final InstalledPackage pkg = (InstalledPackage)value;
296 if (!canUninstallPackage(pkg)) {
297 canUninstall = false;
299 canInstall = canInstallPackage(pkg);
300 if (!canUpgradePackage(pkg)) {
303 final String pyPackageName = pkg.getName();
304 final String availableVersion = (String)myPackagesTable.getValueAt(index, 2);
305 if (!upgradeAvailable) {
306 upgradeAvailable = isUpdateAvailable(pkg.getVersion(), availableVersion) &&
307 !myCurrentlyInstalling.contains(pyPackageName);
309 if (!canUninstall && !canUpgrade) break;
313 myUninstallButton.setEnabled(canUninstall);
314 myInstallButton.setEnabled(canInstall);
315 myUpgradeButton.setEnabled(upgradeAvailable && canUpgrade);
318 protected boolean canUninstallPackage(InstalledPackage pyPackage) {
322 protected boolean canInstallPackage(@NotNull final InstalledPackage pyPackage) {
326 protected boolean canUpgradePackage(InstalledPackage pyPackage) {
330 private void uninstallAction() {
331 final List<InstalledPackage> packages = getSelectedPackages();
332 final PackageManagementService selPackageManagementService = myPackageManagementService;
333 if (selPackageManagementService != null) {
334 PackageManagementService.Listener listener = new PackageManagementService.Listener() {
336 public void operationStarted(String packageName) {
337 UIUtil.invokeLaterIfNeeded(new Runnable() {
340 myPackagesTable.setPaintBusy(true);
346 public void operationFinished(final String packageName,
347 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
348 UIUtil.invokeLaterIfNeeded(new Runnable() {
351 myPackagesTable.clearSelection();
352 updatePackages(selPackageManagementService);
353 myPackagesTable.setPaintBusy(false);
354 if (errorDescription == null) {
355 if (packageName != null) {
356 myNotificationArea.showSuccess("Package '" + packageName + "' successfully uninstalled");
359 myNotificationArea.showSuccess("Packages successfully uninstalled");
363 myNotificationArea.showError("Uninstall packages failed. <a href=\"xxx\">Details...</a>", "Uninstall Packages Failed",
370 myPackageManagementService.uninstallPackages(packages, listener);
375 private List<InstalledPackage> getSelectedPackages() {
376 final List<InstalledPackage> results = new ArrayList<InstalledPackage>();
377 final int[] rows = myPackagesTable.getSelectedRows();
378 for (int row : rows) {
379 final Object packageName = myPackagesTableModel.getValueAt(row, 0);
380 if (packageName instanceof InstalledPackage) {
381 results.add((InstalledPackage)packageName);
387 public void updatePackages(@Nullable PackageManagementService packageManagementService) {
388 myPackageManagementService = packageManagementService;
389 myPackagesTable.clearSelection();
390 myPackagesTableModel.getDataVector().clear();
391 myPackagesTableModel.fireTableDataChanged();
392 if (packageManagementService != null) {
393 doUpdatePackages(packageManagementService);
397 private void onUpdateStarted() {
398 myPackagesTable.setPaintBusy(true);
399 myPackagesTable.getEmptyText().setText("Loading...");
402 private void onUpdateFinished() {
403 myPackagesTable.setPaintBusy(false);
404 myPackagesTable.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT);
405 updateUninstallUpgrade();
406 // Action button presentations won't be updated if no events occur (e.g. mouse isn't moving, keys aren't being pressed).
407 // In that case emulating activity will help:
408 ActivityTracker.getInstance().inc();
411 public void doUpdatePackages(@NotNull final PackageManagementService packageManagementService) {
413 final Application application = ApplicationManager.getApplication();
414 application.executeOnPooledThread(new Runnable() {
417 Collection<InstalledPackage> packages = Lists.newArrayList();
419 packages = packageManagementService.getInstalledPackages();
421 catch (IOException e) {
422 // do nothing, we already have an empty list
425 final Collection<InstalledPackage> finalPackages = packages;
427 final Map<String, RepoPackage> cache = buildNameToPackageMap(packageManagementService.getAllPackagesCached());
428 final boolean shouldFetchLatestVersionsForOnlyInstalledPackages = shouldFetchLatestVersionsForOnlyInstalledPackages();
429 if (cache.isEmpty()) {
430 if (!shouldFetchLatestVersionsForOnlyInstalledPackages) {
431 refreshLatestVersions(packageManagementService);
434 UIUtil.invokeLaterIfNeeded(new Runnable() {
437 if (packageManagementService == myPackageManagementService) {
438 myPackagesTableModel.getDataVector().clear();
439 for (InstalledPackage pkg : finalPackages) {
440 RepoPackage repoPackage = cache.get(pkg.getName());
441 final String version = repoPackage != null ? repoPackage.getLatestVersion() : null;
443 .addRow(new Object[]{pkg, pkg.getVersion(), version == null ? "" : version});
445 if (!cache.isEmpty()) {
448 if (shouldFetchLatestVersionsForOnlyInstalledPackages) {
449 setLatestVersionsForInstalledPackages();
459 private InstalledPackage getInstalledPackageAt(int index) {
460 return (InstalledPackage) myPackagesTableModel.getValueAt(index, 0);
463 private void setLatestVersionsForInstalledPackages() {
464 final PackageManagementServiceEx serviceEx = getServiceEx();
465 if (serviceEx == null) {
468 int packageCount = myPackagesTableModel.getRowCount();
469 if (packageCount == 0) {
472 final AtomicInteger inProgressPackageCount = new AtomicInteger(packageCount);
473 for (int i = 0; i < packageCount; ++i) {
474 final int finalIndex = i;
475 final InstalledPackage pkg = getInstalledPackageAt(finalIndex);
476 serviceEx.fetchLatestVersion(pkg, new CatchingConsumer<String, Exception>() {
478 private void decrement() {
479 if (inProgressPackageCount.decrementAndGet() == 0) {
485 public void consume(Exception e) {
486 UIUtil.invokeLaterIfNeeded(new Runnable() {
495 public void consume(@Nullable final String latestVersion) {
496 UIUtil.invokeLaterIfNeeded(new Runnable() {
499 if (finalIndex < myPackagesTableModel.getRowCount()) {
500 InstalledPackage p = getInstalledPackageAt(finalIndex);
502 myPackagesTableModel.setValueAt(latestVersion, finalIndex, 2);
513 private boolean shouldFetchLatestVersionsForOnlyInstalledPackages() {
514 PackageManagementServiceEx serviceEx = getServiceEx();
515 if (serviceEx != null) {
516 return serviceEx.shouldFetchLatestVersionsForOnlyInstalledPackages();
521 private boolean isUpdateAvailable(@NotNull String currentVersion, @Nullable String availableVersion) {
522 if (availableVersion == null) {
525 PackageManagementService service = myPackageManagementService;
526 if (service != null) {
527 return service.compareVersions(currentVersion, availableVersion) < 0;
529 return PackageVersionComparator.VERSION_COMPARATOR.compare(currentVersion, availableVersion) < 0;
532 private void refreshLatestVersions(@NotNull final PackageManagementService packageManagementService) {
533 final Application application = ApplicationManager.getApplication();
534 application.executeOnPooledThread(new Runnable() {
537 if (packageManagementService == myPackageManagementService) {
539 List<RepoPackage> packages = packageManagementService.reloadAllPackages();
540 final Map<String, RepoPackage> packageMap = buildNameToPackageMap(packages);
541 application.invokeLater(new Runnable() {
544 for (int i = 0; i != myPackagesTableModel.getRowCount(); ++i) {
545 final InstalledPackage pyPackage = (InstalledPackage)myPackagesTableModel.getValueAt(i, 0);
546 final RepoPackage repoPackage = packageMap.get(pyPackage.getName());
547 myPackagesTableModel.setValueAt(repoPackage == null ? null : repoPackage.getLatestVersion(), i, 2);
549 myPackagesTable.setPaintBusy(false);
551 }, ModalityState.stateForComponent(myPackagesTable));
553 catch (IOException ignored) {
554 myPackagesTable.setPaintBusy(false);
561 private static Map<String, RepoPackage> buildNameToPackageMap(List<RepoPackage> packages) {
562 final Map<String, RepoPackage> packageMap = new HashMap<String, RepoPackage>();
563 for (RepoPackage aPackage : packages) {
564 packageMap.put(aPackage.getName(), aPackage);
569 private class MyTableCellRenderer extends DefaultTableCellRenderer {
571 public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
572 final boolean hasFocus, final int row, final int column) {
573 final JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
574 final String version = (String)table.getValueAt(row, 1);
575 final String availableVersion = (String)table.getValueAt(row, 2);
576 boolean update = column == 2 &&
577 StringUtil.isNotEmpty(availableVersion) &&
578 isUpdateAvailable(version, availableVersion);
579 cell.setIcon(update ? AllIcons.Vcs.Arrow_right : null);
580 final Object pyPackage = table.getValueAt(row, 0);
581 if (pyPackage instanceof InstalledPackage) {
582 cell.setToolTipText(((InstalledPackage) pyPackage).getTooltipText());