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