fb72486bfbab5fa90681c355d8e9c97b9b97d095
[idea/community.git] / platform / lang-impl / src / com / intellij / webcore / packaging / InstalledPackagesPanel.java
1 package com.intellij.webcore.packaging;
2
3 import com.google.common.collect.Lists;
4 import com.intellij.icons.AllIcons;
5 import com.intellij.openapi.actionSystem.AnActionEvent;
6 import com.intellij.openapi.application.Application;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.ModalityState;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.projectRoots.Sdk;
11 import com.intellij.openapi.ui.Messages;
12 import com.intellij.openapi.util.text.StringUtil;
13 import com.intellij.ui.*;
14 import com.intellij.ui.table.JBTable;
15 import com.intellij.util.CatchingConsumer;
16 import com.intellij.util.Consumer;
17 import com.intellij.util.IconUtil;
18 import com.intellij.util.ObjectUtils;
19 import com.intellij.util.containers.*;
20 import com.intellij.util.ui.StatusText;
21 import com.intellij.util.ui.UIUtil;
22 import com.intellij.util.containers.ContainerUtil;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import javax.swing.*;
27 import javax.swing.event.ListSelectionEvent;
28 import javax.swing.event.ListSelectionListener;
29 import javax.swing.table.DefaultTableCellRenderer;
30 import javax.swing.table.DefaultTableModel;
31 import javax.swing.table.TableCellRenderer;
32 import java.awt.*;
33 import java.awt.event.MouseEvent;
34 import java.io.IOException;
35 import java.util.*;
36 import java.util.List;
37 import java.util.concurrent.atomic.AtomicInteger;
38
39 public class InstalledPackagesPanel extends JPanel {
40   private final AnActionButton myUpgradeButton;
41   protected final AnActionButton myInstallButton;
42   private final AnActionButton myUninstallButton;
43
44   protected final JBTable myPackagesTable;
45   private DefaultTableModel myPackagesTableModel;
46   protected PackageManagementService myPackageManagementService;
47   protected final Project myProject;
48   protected final PackagesNotificationPanel myNotificationArea;
49   protected final List<Consumer<Sdk>> myPathChangedListeners = ContainerUtil.createLockFreeCopyOnWriteList();
50   private final Set<String> myCurrentlyInstalling = ContainerUtil.newHashSet();
51   private final Set<InstalledPackage> myWaitingToUpgrade = ContainerUtil.newHashSet();
52
53   public InstalledPackagesPanel(Project project, PackagesNotificationPanel area) {
54     super(new BorderLayout());
55     myProject = project;
56     myNotificationArea = area;
57
58     myPackagesTableModel = new DefaultTableModel(new String[]{"Package", "Version", "Latest"}, 0) {
59       @Override
60       public boolean isCellEditable(int i, int i1) {
61         return false;
62       }
63     };
64     final TableCellRenderer tableCellRenderer = new MyTableCellRenderer();
65     myPackagesTable = new JBTable(myPackagesTableModel) {
66       @Override
67       public TableCellRenderer getCellRenderer(int row, int column) {
68         return tableCellRenderer;
69       }
70     };
71     myPackagesTable.getTableHeader().setReorderingAllowed(false);
72
73     myUpgradeButton = new AnActionButton("Upgrade", IconUtil.getMoveUpIcon()) {
74       @Override
75       public void actionPerformed(AnActionEvent e) {
76         upgradeAction();
77       }
78     };
79     final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myPackagesTable).disableUpDownActions()
80       .setAddAction(new AnActionButtonRunnable() {
81         @Override
82         public void run(AnActionButton button) {
83           if (myPackageManagementService != null) {
84             ManagePackagesDialog dialog = createManagePackagesDialog();
85             dialog.show();
86           }
87         }
88       })
89       .setRemoveAction(new AnActionButtonRunnable() {
90         @Override
91         public void run(AnActionButton button) {
92           uninstallAction();
93         }
94       })
95       .addExtraAction(myUpgradeButton);
96
97     decorator.setPreferredSize(new Dimension(500, 500));
98     add(decorator.createPanel());
99     myInstallButton = decorator.getActionsPanel().getAnActionButton(CommonActionsPanel.Buttons.ADD);
100     myUninstallButton = decorator.getActionsPanel().getAnActionButton(CommonActionsPanel.Buttons.REMOVE);
101     myInstallButton.setEnabled(false);
102     myUninstallButton.setEnabled(false);
103     myUpgradeButton.setEnabled(false);
104
105     myInstallButton.getTemplatePresentation().setText("Install");
106     myUninstallButton.getTemplatePresentation().setText("Uninstall");
107
108
109     myPackagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
110       @Override
111       public void valueChanged(ListSelectionEvent event) {
112         updateUninstallUpgrade();
113       }
114     });
115
116     new DoubleClickListener() {
117       @Override
118       protected boolean onDoubleClick(MouseEvent e) {
119         if (myPackageManagementService != null && myInstallButton.isEnabled()) {
120           ManagePackagesDialog dialog = createManagePackagesDialog();
121           Point p = e.getPoint();
122           int row = myPackagesTable.rowAtPoint(p);
123           int column = myPackagesTable.columnAtPoint(p);
124           if (row >= 0 && column >= 0) {
125             Object pkg = myPackagesTable.getValueAt(row, 0);
126             if (pkg instanceof InstalledPackage) {
127               dialog.selectPackage((InstalledPackage) pkg);
128             }
129           }
130           dialog.show();
131           return true;
132         }
133         return false;
134       }
135     }.installOn(myPackagesTable);
136   }
137
138   private ManagePackagesDialog createManagePackagesDialog() {
139     return new ManagePackagesDialog(myProject,
140                                     myPackageManagementService,
141                                     new PackageManagementService.Listener() {
142                                       @Override
143                                       public void operationStarted(String packageName) {
144                                         myPackagesTable.setPaintBusy(true);
145                                       }
146
147                                       @Override
148                                       public void operationFinished(String packageName,
149                                                                     @Nullable String errorDescription) {
150                                         myNotificationArea.showResult(packageName, errorDescription);
151                                         myPackagesTable.clearSelection();
152                                         doUpdatePackages(myPackageManagementService);
153                                       }
154                                     });
155   }
156
157   public void addPathChangedListener(Consumer<Sdk> consumer) {
158     myPathChangedListeners.add(consumer);
159   }
160
161   private void upgradeAction() {
162     final int[] rows = myPackagesTable.getSelectedRows();
163     if (myPackageManagementService != null) {
164       final Set<String> upgradedPackages = new HashSet<String>();
165       final Set<String> packagesShouldBePostponed = getPackagesToPostpone();
166       for (int row : rows) {
167         final Object packageObj = myPackagesTableModel.getValueAt(row, 0);
168         if (packageObj instanceof InstalledPackage) {
169           InstalledPackage pkg = (InstalledPackage)packageObj;
170           final String packageName = pkg.getName();
171           final String currentVersion = pkg.getVersion();
172           final String availableVersion = (String)myPackagesTableModel.getValueAt(row, 2);
173
174           if (packagesShouldBePostponed.contains(packageName)) {
175             myWaitingToUpgrade.add((InstalledPackage)packageObj);
176           }
177           else if (PackageVersionComparator.VERSION_COMPARATOR.compare(currentVersion, availableVersion) < 0) {
178             upgradePackage(pkg, availableVersion);
179             upgradedPackages.add(packageName);
180           }
181         }
182       }
183
184       if (myCurrentlyInstalling.isEmpty() && upgradedPackages.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
185         upgradePostponedPackages();
186       }
187     }
188   }
189
190   private void upgradePostponedPackages() {
191     final Iterator<InstalledPackage> iterator = myWaitingToUpgrade.iterator();
192     final InstalledPackage toUpgrade = iterator.next();
193     iterator.remove();
194     upgradePackage(toUpgrade, toUpgrade.getVersion());
195   }
196
197   protected Set<String> getPackagesToPostpone() {
198     return Collections.emptySet();
199   }
200
201   private void upgradePackage(@NotNull final InstalledPackage pkg, @Nullable final String toVersion) {
202     final PackageManagementService selPackageManagementService = myPackageManagementService;
203     myPackageManagementService.fetchPackageVersions(pkg.getName(), new CatchingConsumer<List<String>, Exception>() {
204       @Override
205       public void consume(List<String> releases) {
206         if (!releases.isEmpty() &&
207             PackageVersionComparator.VERSION_COMPARATOR.compare(pkg.getVersion(), releases.get(0)) >= 0) {
208           return;
209         }
210
211         ApplicationManager.getApplication().invokeLater(new Runnable() {
212           @Override
213           public void run() {
214             final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
215               @Override
216               public void operationStarted(final String packageName) {
217                 UIUtil.invokeLaterIfNeeded(new Runnable() {
218                   @Override
219                   public void run() {
220                     myPackagesTable.setPaintBusy(true);
221                     myCurrentlyInstalling.add(packageName);
222                   }
223                 });
224               }
225
226               @Override
227               public void operationFinished(final String packageName, @Nullable final String errorDescription) {
228                 UIUtil.invokeLaterIfNeeded(new Runnable() {
229                   @Override
230                   public void run() {
231                     myPackagesTable.clearSelection();
232                     updatePackages(selPackageManagementService);
233                     myPackagesTable.setPaintBusy(false);
234                     myCurrentlyInstalling.remove(packageName);
235                     if (errorDescription == null) {
236                       myNotificationArea.showSuccess("Package " + packageName + " successfully upgraded");
237                     }
238                     else {
239                       myNotificationArea.showError("Upgrade packages failed. <a href=\"xxx\">Details...</a>",
240                                                    "Upgrade Packages Failed",
241                                                    "Upgrade packages failed.\n" + errorDescription);
242                     }
243
244                     if (myCurrentlyInstalling.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
245                       upgradePostponedPackages();
246                     }
247                   }
248                 });
249               }
250             };
251             PackageManagementServiceEx serviceEx = getServiceEx();
252             if (serviceEx != null) {
253               serviceEx.updatePackage(pkg, toVersion, listener);
254             }
255             else {
256               myPackageManagementService.installPackage(new RepoPackage(pkg.getName(), null /* TODO? */), null, true, null, listener, false);
257             }
258             myUpgradeButton.setEnabled(false);
259           }
260         }, ModalityState.any());
261       }
262
263       @Override
264       public void consume(Exception e) {
265         ApplicationManager.getApplication().invokeLater(new Runnable() {
266           @Override
267           public void run() {
268             Messages.showErrorDialog("Error occurred. Please, check your internet connection.",
269                                      "Upgrade Package Failed.");
270           }
271         }, ModalityState.any());
272       }
273     });
274   }
275
276   @Nullable
277   private PackageManagementServiceEx getServiceEx() {
278     return ObjectUtils.tryCast(myPackageManagementService, PackageManagementServiceEx.class);
279   }
280
281   private void updateUninstallUpgrade() {
282     ApplicationManager.getApplication().invokeLater(new Runnable() {
283       @Override
284       public void run() {
285         final int[] selected = myPackagesTable.getSelectedRows();
286         boolean upgradeAvailable = false;
287         boolean canUninstall = selected.length != 0;
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;
298               }
299               if (!canUpgradePackage(pkg)) {
300                 canUpgrade = false;
301               }
302               final String pyPackageName = pkg.getName();
303               final String availableVersion = (String)myPackagesTable.getValueAt(index, 2);
304               if (!upgradeAvailable) {
305                 upgradeAvailable = PackageVersionComparator.VERSION_COMPARATOR.compare(pkg.getVersion(), availableVersion) < 0 &&
306                                    !myCurrentlyInstalling.contains(pyPackageName);
307               }
308               if (!canUninstall && !canUpgrade) break;
309             }
310           }
311         }
312         myUninstallButton.setEnabled(canUninstall);
313         myUpgradeButton.setEnabled(upgradeAvailable && canUpgrade);
314       }
315     }, ModalityState.any());
316   }
317
318   protected boolean canUninstallPackage(InstalledPackage pyPackage) {
319     return true;
320   }
321
322   protected boolean canUpgradePackage(InstalledPackage pyPackage) {
323     return true;
324   }
325
326   private void uninstallAction() {
327     final List<InstalledPackage> packages = getSelectedPackages();
328     final PackageManagementService selPackageManagementService = myPackageManagementService;
329     if (selPackageManagementService != null) {
330       PackageManagementService.Listener listener = new PackageManagementService.Listener() {
331         @Override
332         public void operationStarted(String packageName) {
333           UIUtil.invokeLaterIfNeeded(new Runnable() {
334             @Override
335             public void run() {
336               myPackagesTable.setPaintBusy(true);
337             }
338           });
339         }
340
341         @Override
342         public void operationFinished(final String packageName, @Nullable final String errorDescription) {
343           UIUtil.invokeLaterIfNeeded(new Runnable() {
344             @Override
345             public void run() {
346               myPackagesTable.clearSelection();
347               updatePackages(selPackageManagementService);
348               myPackagesTable.setPaintBusy(false);
349               if (errorDescription == null) {
350                 if (packageName != null) {
351                   myNotificationArea.showSuccess("Package '" + packageName + "' successfully uninstalled");
352                 }
353                 else {
354                   myNotificationArea.showSuccess("Packages successfully uninstalled");
355                 }
356               }
357               else {
358                 myNotificationArea.showError("Uninstall packages failed. <a href=\"xxx\">Details...</a>",
359                                              "Uninstall Packages Failed",
360                                              "Uninstall packages failed.\n" + errorDescription);
361               }
362             }
363           });
364         }
365       };
366       myPackageManagementService.uninstallPackages(packages, listener);
367     }
368   }
369
370   @NotNull
371   private List<InstalledPackage> getSelectedPackages() {
372     final List<InstalledPackage> results = new ArrayList<InstalledPackage>();
373     final int[] rows = myPackagesTable.getSelectedRows();
374     for (int row : rows) {
375       final Object packageName = myPackagesTableModel.getValueAt(row, 0);
376       if (packageName instanceof InstalledPackage) {
377         results.add((InstalledPackage)packageName);
378       }
379     }
380     return results;
381   }
382
383   public void updatePackages(@Nullable PackageManagementService packageManagementService) {
384     myPackageManagementService = packageManagementService;
385     myPackagesTable.clearSelection();
386     myPackagesTableModel.getDataVector().clear();
387     myPackagesTableModel.fireTableDataChanged();
388     if (packageManagementService != null) {
389       doUpdatePackages(packageManagementService);
390     }
391   }
392
393   private void onUpdateStarted() {
394     myPackagesTable.setPaintBusy(true);
395     myPackagesTable.getEmptyText().setText("Loading...");
396   }
397
398   private void onUpdateFinished() {
399     myPackagesTable.setPaintBusy(false);
400     myPackagesTable.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT);
401   }
402
403   public void doUpdatePackages(@NotNull final PackageManagementService packageManagementService) {
404     onUpdateStarted();
405     final Application application = ApplicationManager.getApplication();
406     application.executeOnPooledThread(new Runnable() {
407       @Override
408       public void run() {
409         Collection<InstalledPackage> packages = Lists.newArrayList();
410         try {
411           packages = packageManagementService.getInstalledPackages();
412         }
413         catch (IOException e) {
414           // do nothing, we already have an empty list
415         }
416         finally {
417           final Collection<InstalledPackage> finalPackages = packages;
418
419           final Map<String, RepoPackage> cache = buildNameToPackageMap(packageManagementService.getAllPackagesCached());
420           final boolean shouldFetchLatestVersionsForOnlyInstalledPackages = shouldFetchLatestVersionsForOnlyInstalledPackages();
421           if (cache.isEmpty()) {
422             if (!shouldFetchLatestVersionsForOnlyInstalledPackages) {
423               refreshLatestVersions();
424             }
425           }
426           UIUtil.invokeLaterIfNeeded(new Runnable() {
427             @Override
428             public void run() {
429               if (packageManagementService == myPackageManagementService) {
430                 myPackagesTableModel.getDataVector().clear();
431                 for (InstalledPackage pkg : finalPackages) {
432                   RepoPackage repoPackage = cache.get(pkg.getName());
433                   final String version = repoPackage != null ? repoPackage.getLatestVersion() : null;
434                   myPackagesTableModel
435                     .addRow(new Object[]{pkg, pkg.getVersion(), version == null ? "" : version});
436                 }
437                 if (!cache.isEmpty()) {
438                   onUpdateFinished();
439                 }
440                 if (shouldFetchLatestVersionsForOnlyInstalledPackages) {
441                   setLatestVersionsForInstalledPackages();
442                 }
443               }
444             }
445           });
446         }
447       }
448     });
449   }
450
451   private InstalledPackage getInstalledPackageAt(int index) {
452     return (InstalledPackage) myPackagesTableModel.getValueAt(index, 0);
453   }
454
455   private void setLatestVersionsForInstalledPackages() {
456     final PackageManagementServiceEx serviceEx = getServiceEx();
457     if (serviceEx == null) {
458       return;
459     }
460     int packageCount = myPackagesTableModel.getRowCount();
461     if (packageCount == 0) {
462       onUpdateFinished();
463     }
464     final AtomicInteger inProgressPackageCount = new AtomicInteger(packageCount);
465     for (int i = 0; i < packageCount; ++i) {
466       final int finalIndex = i;
467       final InstalledPackage pkg = getInstalledPackageAt(finalIndex);
468       serviceEx.fetchLatestVersion(pkg, new CatchingConsumer<String, Exception>() {
469
470         private void decrement() {
471           if (inProgressPackageCount.decrementAndGet() == 0) {
472             UIUtil.invokeLaterIfNeeded(new Runnable() {
473               @Override
474               public void run() {
475                 onUpdateFinished();
476               }
477             });
478           }
479         }
480
481         @Override
482         public void consume(Exception e) {
483           decrement();
484         }
485
486         @Override
487         public void consume(@Nullable final String latestVersion) {
488           if (latestVersion == null) {
489             decrement();
490             return;
491           }
492           ApplicationManager.getApplication().invokeLater(new Runnable() {
493             @Override
494             public void run() {
495               if (finalIndex < myPackagesTableModel.getRowCount()) {
496                 InstalledPackage p = getInstalledPackageAt(finalIndex);
497                 if (pkg == p) {
498                   myPackagesTableModel.setValueAt(latestVersion, finalIndex, 2);
499                 }
500               }
501               decrement();
502             }
503           }, ModalityState.any());
504         }
505       });
506     }
507   }
508
509   private boolean shouldFetchLatestVersionsForOnlyInstalledPackages() {
510     PackageManagementServiceEx serviceEx = getServiceEx();
511     if (serviceEx != null) {
512       return serviceEx.shouldFetchLatestVersionsForOnlyInstalledPackages();
513     }
514     return false;
515   }
516
517   private void refreshLatestVersions() {
518     final Application application = ApplicationManager.getApplication();
519     application.executeOnPooledThread(new Runnable() {
520       @Override
521       public void run() {
522         try {
523           List<RepoPackage> packages = myPackageManagementService.reloadAllPackages();
524           final Map<String, RepoPackage> packageMap = buildNameToPackageMap(packages);
525           application.invokeLater(new Runnable() {
526             @Override
527             public void run() {
528               for (int i = 0; i != myPackagesTableModel.getRowCount(); ++i) {
529                 final InstalledPackage pyPackage = (InstalledPackage)myPackagesTableModel.getValueAt(i, 0);
530                 final RepoPackage repoPackage = packageMap.get(pyPackage.getName());
531                 myPackagesTableModel.setValueAt(repoPackage == null ? null : repoPackage.getLatestVersion(), i, 2);
532               }
533               myPackagesTable.setPaintBusy(false);
534             }
535           }, ModalityState.stateForComponent(myPackagesTable));
536         }
537         catch (IOException ignored) {
538           myPackagesTable.setPaintBusy(false);
539         }
540       }
541     });
542   }
543
544   private static Map<String, RepoPackage> buildNameToPackageMap(List<RepoPackage> packages) {
545     final Map<String, RepoPackage> packageMap = new HashMap<String, RepoPackage>();
546     for (RepoPackage aPackage : packages) {
547       packageMap.put(aPackage.getName(), aPackage);
548     }
549     return packageMap;
550   }
551
552   private static class MyTableCellRenderer extends DefaultTableCellRenderer {
553     @Override
554     public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
555                                                    final boolean hasFocus, final int row, final int column) {
556       final JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
557       final String version = (String)table.getValueAt(row, 1);
558       final String availableVersion = (String)table.getValueAt(row, 2);
559       boolean update = column == 2 &&
560                        StringUtil.isNotEmpty(availableVersion) &&
561                        PackageVersionComparator.VERSION_COMPARATOR.compare(version, availableVersion) < 0;
562       cell.setIcon(update ? AllIcons.Vcs.Arrow_right : null);
563       final Object pyPackage = table.getValueAt(row, 0);
564       if (pyPackage instanceof InstalledPackage) {
565         cell.setToolTipText(((InstalledPackage) pyPackage).getTooltipText());
566       }
567       return cell;
568     }
569   }
570 }