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