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.AnActionButton;
15 import com.intellij.ui.DoubleClickListener;
16 import com.intellij.ui.ToolbarDecorator;
17 import com.intellij.ui.table.JBTable;
18 import com.intellij.util.CatchingConsumer;
19 import com.intellij.util.Consumer;
20 import com.intellij.util.IconUtil;
21 import com.intellij.util.ObjectUtils;
22 import com.intellij.util.containers.ContainerUtil;
23 import com.intellij.util.containers.HashSet;
24 import com.intellij.util.ui.StatusText;
25 import com.intellij.util.ui.UIUtil;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
30 import javax.swing.event.ListSelectionEvent;
31 import javax.swing.event.ListSelectionListener;
32 import javax.swing.table.DefaultTableCellRenderer;
33 import javax.swing.table.DefaultTableModel;
34 import javax.swing.table.TableCellRenderer;
36 import java.awt.event.MouseEvent;
37 import java.io.IOException;
39 import java.util.List;
40 import java.util.concurrent.atomic.AtomicInteger;
42 public class InstalledPackagesPanel extends JPanel {
43 private final AnActionButton myUpgradeButton;
44 protected final AnActionButton myInstallButton;
45 private final AnActionButton myUninstallButton;
47 protected final JBTable myPackagesTable;
48 private DefaultTableModel myPackagesTableModel;
49 // can be accessed from any thread
50 protected volatile PackageManagementService myPackageManagementService;
51 protected final Project myProject;
52 protected final PackagesNotificationPanel myNotificationArea;
53 protected final List<Consumer<Sdk>> myPathChangedListeners = ContainerUtil.createLockFreeCopyOnWriteList();
54 private final Set<String> myCurrentlyInstalling = ContainerUtil.newHashSet();
55 private final Set<InstalledPackage> myWaitingToUpgrade = ContainerUtil.newHashSet();
57 public InstalledPackagesPanel(Project project, PackagesNotificationPanel area) {
58 super(new BorderLayout());
60 myNotificationArea = area;
62 myPackagesTableModel = new DefaultTableModel(new String[]{"Package", "Version", "Latest"}, 0) {
64 public boolean isCellEditable(int i, int i1) {
68 final TableCellRenderer tableCellRenderer = new MyTableCellRenderer();
69 myPackagesTable = new JBTable(myPackagesTableModel) {
71 public TableCellRenderer getCellRenderer(int row, int column) {
72 return tableCellRenderer;
75 // Defence from javax.swing.JTable.initializeLocalVars:
76 // setPreferredScrollableViewportSize(new Dimension(450, 400));
77 myPackagesTable.setPreferredScrollableViewportSize(null);
78 myPackagesTable.setStriped(true);
79 myPackagesTable.getTableHeader().setReorderingAllowed(false);
81 myUpgradeButton = new AnActionButton("Upgrade", IconUtil.getMoveUpIcon()) {
83 public void actionPerformed(@NotNull AnActionEvent e) {
87 myInstallButton = new AnActionButton("Install", IconUtil.getAddIcon()) {
89 public void actionPerformed(@NotNull AnActionEvent e) {
90 if (myPackageManagementService != null) {
91 ManagePackagesDialog dialog = createManagePackagesDialog();
96 myUninstallButton = new AnActionButton("Uninstall", IconUtil.getRemoveIcon()) {
98 public void actionPerformed(@NotNull AnActionEvent e) {
102 ToolbarDecorator decorator =
103 ToolbarDecorator.createDecorator(myPackagesTable).disableUpDownActions().disableAddAction().disableRemoveAction()
104 .addExtraAction(myInstallButton)
105 .addExtraAction(myUninstallButton)
106 .addExtraAction(myUpgradeButton);
108 add(decorator.createPanel());
109 myInstallButton.setEnabled(false);
110 myUninstallButton.setEnabled(false);
111 myUpgradeButton.setEnabled(false);
113 myPackagesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
115 public void valueChanged(ListSelectionEvent event) {
116 updateUninstallUpgrade();
120 new DoubleClickListener() {
122 protected boolean onDoubleClick(MouseEvent e) {
123 if (myPackageManagementService != null && myInstallButton.isEnabled()) {
124 ManagePackagesDialog dialog = createManagePackagesDialog();
125 Point p = e.getPoint();
126 int row = myPackagesTable.rowAtPoint(p);
127 int column = myPackagesTable.columnAtPoint(p);
128 if (row >= 0 && column >= 0) {
129 Object pkg = myPackagesTable.getValueAt(row, 0);
130 if (pkg instanceof InstalledPackage) {
131 dialog.selectPackage((InstalledPackage) pkg);
139 }.installOn(myPackagesTable);
143 protected ManagePackagesDialog createManagePackagesDialog() {
144 return new ManagePackagesDialog(myProject,
145 myPackageManagementService,
146 new PackageManagementService.Listener() {
148 public void operationStarted(String packageName) {
149 myPackagesTable.setPaintBusy(true);
153 public void operationFinished(String packageName,
154 @Nullable PackageManagementService.ErrorDescription errorDescription) {
155 myNotificationArea.showResult(packageName, errorDescription);
156 myPackagesTable.clearSelection();
157 doUpdatePackages(myPackageManagementService);
162 public void addPathChangedListener(Consumer<Sdk> consumer) {
163 myPathChangedListeners.add(consumer);
166 private void upgradeAction() {
167 final int[] rows = myPackagesTable.getSelectedRows();
168 if (myPackageManagementService != null) {
169 final Set<String> upgradedPackages = new HashSet<String>();
170 final Set<String> packagesShouldBePostponed = getPackagesToPostpone();
171 for (int row : rows) {
172 final Object packageObj = myPackagesTableModel.getValueAt(row, 0);
173 if (packageObj instanceof InstalledPackage) {
174 InstalledPackage pkg = (InstalledPackage)packageObj;
175 final String packageName = pkg.getName();
176 final String currentVersion = pkg.getVersion();
177 final String availableVersion = (String)myPackagesTableModel.getValueAt(row, 2);
179 if (packagesShouldBePostponed.contains(packageName)) {
180 myWaitingToUpgrade.add((InstalledPackage)packageObj);
182 else if (isUpdateAvailable(currentVersion, availableVersion)) {
183 upgradePackage(pkg, availableVersion);
184 upgradedPackages.add(packageName);
189 if (myCurrentlyInstalling.isEmpty() && upgradedPackages.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
190 upgradePostponedPackages();
195 private void upgradePostponedPackages() {
196 final Iterator<InstalledPackage> iterator = myWaitingToUpgrade.iterator();
197 final InstalledPackage toUpgrade = iterator.next();
199 upgradePackage(toUpgrade, toUpgrade.getVersion());
202 protected Set<String> getPackagesToPostpone() {
203 return Collections.emptySet();
206 private void upgradePackage(@NotNull final InstalledPackage pkg, @Nullable final String toVersion) {
207 final PackageManagementService selPackageManagementService = myPackageManagementService;
208 myPackageManagementService.fetchPackageVersions(pkg.getName(), new CatchingConsumer<List<String>, Exception>() {
210 public void consume(List<String> releases) {
211 if (!releases.isEmpty() && !isUpdateAvailable(pkg.getVersion(), releases.get(0))) {
215 ApplicationManager.getApplication().invokeLater(new Runnable() {
218 final PackageManagementService.Listener listener = new PackageManagementService.Listener() {
220 public void operationStarted(final String packageName) {
221 UIUtil.invokeLaterIfNeeded(new Runnable() {
224 myPackagesTable.setPaintBusy(true);
225 myCurrentlyInstalling.add(packageName);
231 public void operationFinished(final String packageName,
232 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
233 UIUtil.invokeLaterIfNeeded(new Runnable() {
236 myPackagesTable.clearSelection();
237 updatePackages(selPackageManagementService);
238 myPackagesTable.setPaintBusy(false);
239 myCurrentlyInstalling.remove(packageName);
240 if (errorDescription == null) {
241 myNotificationArea.showSuccess("Package " + packageName + " successfully upgraded");
244 myNotificationArea.showError("Upgrade packages failed. <a href=\"xxx\">Details...</a>", "Upgrade Packages Failed",
248 if (myCurrentlyInstalling.isEmpty() && !myWaitingToUpgrade.isEmpty()) {
249 upgradePostponedPackages();
255 PackageManagementServiceEx serviceEx = getServiceEx();
256 if (serviceEx != null) {
257 serviceEx.updatePackage(pkg, toVersion, listener);
260 myPackageManagementService.installPackage(new RepoPackage(pkg.getName(), null /* TODO? */), null, true, null, listener, false);
262 myUpgradeButton.setEnabled(false);
264 }, ModalityState.any());
268 public void consume(Exception e) {
269 ApplicationManager.getApplication().invokeLater(new Runnable() {
272 Messages.showErrorDialog("Error occurred. Please, check your internet connection.",
273 "Upgrade Package Failed.");
275 }, ModalityState.any());
281 private PackageManagementServiceEx getServiceEx() {
282 return ObjectUtils.tryCast(myPackageManagementService, PackageManagementServiceEx.class);
285 private void updateUninstallUpgrade() {
286 final int[] selected = myPackagesTable.getSelectedRows();
287 boolean upgradeAvailable = false;
288 boolean canUninstall = selected.length != 0;
289 boolean canInstall = true;
290 boolean canUpgrade = true;
291 if (myPackageManagementService != null && selected.length != 0) {
292 for (int i = 0; i != selected.length; ++i) {
293 final int index = selected[i];
294 if (index >= myPackagesTable.getRowCount()) continue;
295 final Object value = myPackagesTable.getValueAt(index, 0);
296 if (value instanceof InstalledPackage) {
297 final InstalledPackage pkg = (InstalledPackage)value;
298 if (!canUninstallPackage(pkg)) {
299 canUninstall = false;
301 canInstall = canInstallPackage(pkg);
302 if (!canUpgradePackage(pkg)) {
305 final String pyPackageName = pkg.getName();
306 final String availableVersion = (String)myPackagesTable.getValueAt(index, 2);
307 if (!upgradeAvailable) {
308 upgradeAvailable = isUpdateAvailable(pkg.getVersion(), availableVersion) &&
309 !myCurrentlyInstalling.contains(pyPackageName);
311 if (!canUninstall && !canUpgrade) break;
315 myUninstallButton.setEnabled(canUninstall);
316 myInstallButton.setEnabled(canInstall);
317 myUpgradeButton.setEnabled(upgradeAvailable && canUpgrade);
320 protected boolean canUninstallPackage(InstalledPackage pyPackage) {
324 protected boolean canInstallPackage(@NotNull final InstalledPackage pyPackage) {
328 protected boolean canUpgradePackage(InstalledPackage pyPackage) {
332 private void uninstallAction() {
333 final List<InstalledPackage> packages = getSelectedPackages();
334 final PackageManagementService selPackageManagementService = myPackageManagementService;
335 if (selPackageManagementService != null) {
336 PackageManagementService.Listener listener = new PackageManagementService.Listener() {
338 public void operationStarted(String packageName) {
339 UIUtil.invokeLaterIfNeeded(new Runnable() {
342 myPackagesTable.setPaintBusy(true);
348 public void operationFinished(final String packageName,
349 @Nullable final PackageManagementService.ErrorDescription errorDescription) {
350 UIUtil.invokeLaterIfNeeded(new Runnable() {
353 myPackagesTable.clearSelection();
354 updatePackages(selPackageManagementService);
355 myPackagesTable.setPaintBusy(false);
356 if (errorDescription == null) {
357 if (packageName != null) {
358 myNotificationArea.showSuccess("Package '" + packageName + "' successfully uninstalled");
361 myNotificationArea.showSuccess("Packages successfully uninstalled");
365 myNotificationArea.showError("Uninstall packages failed. <a href=\"xxx\">Details...</a>", "Uninstall Packages Failed",
372 myPackageManagementService.uninstallPackages(packages, listener);
377 private List<InstalledPackage> getSelectedPackages() {
378 final List<InstalledPackage> results = new ArrayList<InstalledPackage>();
379 final int[] rows = myPackagesTable.getSelectedRows();
380 for (int row : rows) {
381 final Object packageName = myPackagesTableModel.getValueAt(row, 0);
382 if (packageName instanceof InstalledPackage) {
383 results.add((InstalledPackage)packageName);
389 public void updatePackages(@Nullable PackageManagementService packageManagementService) {
390 myPackageManagementService = packageManagementService;
391 myPackagesTable.clearSelection();
392 myPackagesTableModel.getDataVector().clear();
393 myPackagesTableModel.fireTableDataChanged();
394 if (packageManagementService != null) {
395 doUpdatePackages(packageManagementService);
399 private void onUpdateStarted() {
400 myPackagesTable.setPaintBusy(true);
401 myPackagesTable.getEmptyText().setText("Loading...");
404 private void onUpdateFinished() {
405 myPackagesTable.setPaintBusy(false);
406 myPackagesTable.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT);
407 updateUninstallUpgrade();
408 // Action button presentations won't be updated if no events occur (e.g. mouse isn't moving, keys aren't being pressed).
409 // In that case emulating activity will help:
410 ActivityTracker.getInstance().inc();
413 public void doUpdatePackages(@NotNull final PackageManagementService packageManagementService) {
415 final Application application = ApplicationManager.getApplication();
416 application.executeOnPooledThread(new Runnable() {
419 Collection<InstalledPackage> packages = Lists.newArrayList();
421 packages = packageManagementService.getInstalledPackages();
423 catch (IOException e) {
424 // do nothing, we already have an empty list
427 final Collection<InstalledPackage> finalPackages = packages;
429 final Map<String, RepoPackage> cache = buildNameToPackageMap(packageManagementService.getAllPackagesCached());
430 final boolean shouldFetchLatestVersionsForOnlyInstalledPackages = shouldFetchLatestVersionsForOnlyInstalledPackages();
431 if (cache.isEmpty()) {
432 if (!shouldFetchLatestVersionsForOnlyInstalledPackages) {
433 refreshLatestVersions(packageManagementService);
436 UIUtil.invokeLaterIfNeeded(new Runnable() {
439 if (packageManagementService == myPackageManagementService) {
440 myPackagesTableModel.getDataVector().clear();
441 for (InstalledPackage pkg : finalPackages) {
442 RepoPackage repoPackage = cache.get(pkg.getName());
443 final String version = repoPackage != null ? repoPackage.getLatestVersion() : null;
445 .addRow(new Object[]{pkg, pkg.getVersion(), version == null ? "" : version});
447 if (!cache.isEmpty()) {
450 if (shouldFetchLatestVersionsForOnlyInstalledPackages) {
451 setLatestVersionsForInstalledPackages();
461 private InstalledPackage getInstalledPackageAt(int index) {
462 return (InstalledPackage) myPackagesTableModel.getValueAt(index, 0);
465 private void setLatestVersionsForInstalledPackages() {
466 final PackageManagementServiceEx serviceEx = getServiceEx();
467 if (serviceEx == null) {
470 int packageCount = myPackagesTableModel.getRowCount();
471 if (packageCount == 0) {
474 final AtomicInteger inProgressPackageCount = new AtomicInteger(packageCount);
475 for (int i = 0; i < packageCount; ++i) {
476 final int finalIndex = i;
477 final InstalledPackage pkg = getInstalledPackageAt(finalIndex);
478 serviceEx.fetchLatestVersion(pkg, new CatchingConsumer<String, Exception>() {
480 private void decrement() {
481 if (inProgressPackageCount.decrementAndGet() == 0) {
487 public void consume(Exception e) {
488 UIUtil.invokeLaterIfNeeded(new Runnable() {
497 public void consume(@Nullable final String latestVersion) {
498 UIUtil.invokeLaterIfNeeded(new Runnable() {
501 if (finalIndex < myPackagesTableModel.getRowCount()) {
502 InstalledPackage p = getInstalledPackageAt(finalIndex);
504 myPackagesTableModel.setValueAt(latestVersion, finalIndex, 2);
515 private boolean shouldFetchLatestVersionsForOnlyInstalledPackages() {
516 PackageManagementServiceEx serviceEx = getServiceEx();
517 if (serviceEx != null) {
518 return serviceEx.shouldFetchLatestVersionsForOnlyInstalledPackages();
523 private boolean isUpdateAvailable(@Nullable String currentVersion, @Nullable String availableVersion) {
524 if (availableVersion == null) {
527 if (currentVersion == null) {
530 PackageManagementService service = myPackageManagementService;
531 if (service != null) {
532 return service.compareVersions(currentVersion, availableVersion) < 0;
534 return PackageVersionComparator.VERSION_COMPARATOR.compare(currentVersion, availableVersion) < 0;
537 private void refreshLatestVersions(@NotNull final PackageManagementService packageManagementService) {
538 final Application application = ApplicationManager.getApplication();
539 application.executeOnPooledThread(new Runnable() {
542 if (packageManagementService == myPackageManagementService) {
544 List<RepoPackage> packages = packageManagementService.reloadAllPackages();
545 final Map<String, RepoPackage> packageMap = buildNameToPackageMap(packages);
546 application.invokeLater(new Runnable() {
549 for (int i = 0; i != myPackagesTableModel.getRowCount(); ++i) {
550 final InstalledPackage pyPackage = (InstalledPackage)myPackagesTableModel.getValueAt(i, 0);
551 final RepoPackage repoPackage = packageMap.get(pyPackage.getName());
552 myPackagesTableModel.setValueAt(repoPackage == null ? null : repoPackage.getLatestVersion(), i, 2);
554 myPackagesTable.setPaintBusy(false);
556 }, ModalityState.stateForComponent(myPackagesTable));
558 catch (IOException ignored) {
559 myPackagesTable.setPaintBusy(false);
566 private static Map<String, RepoPackage> buildNameToPackageMap(List<RepoPackage> packages) {
567 final Map<String, RepoPackage> packageMap = new HashMap<String, RepoPackage>();
568 for (RepoPackage aPackage : packages) {
569 packageMap.put(aPackage.getName(), aPackage);
574 private class MyTableCellRenderer extends DefaultTableCellRenderer {
576 public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
577 final boolean hasFocus, final int row, final int column) {
578 final JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
579 final String version = (String)table.getValueAt(row, 1);
580 final String availableVersion = (String)table.getValueAt(row, 2);
581 boolean update = column == 2 &&
582 StringUtil.isNotEmpty(availableVersion) &&
583 isUpdateAvailable(version, availableVersion);
584 cell.setIcon(update ? AllIcons.Vcs.Arrow_right : null);
585 final Object pyPackage = table.getValueAt(row, 0);
586 if (pyPackage instanceof InstalledPackage) {
587 cell.setToolTipText(((InstalledPackage) pyPackage).getTooltipText());