/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.updateSettings.impl.pluginsAdvertisement; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.intellij.ide.BrowserUtil; import com.intellij.ide.plugins.*; import com.intellij.ide.util.PropertiesComponent; import com.intellij.notification.*; import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.fileTypes.FileTypeFactory; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.updateSettings.impl.PluginDownloader; import com.intellij.openapi.updateSettings.impl.UpdateSettings; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.reference.SoftReference; import com.intellij.ui.EditorNotifications; import com.intellij.util.PlatformUtils; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.xmlb.XmlSerializer; import com.intellij.util.xmlb.annotations.MapAnnotation; import com.intellij.util.xmlb.annotations.Tag; import org.jdom.Document; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.util.*; public class PluginsAdvertiser implements StartupActivity { @NonNls public static final String IGNORE_ULTIMATE_EDITION = "ignoreUltimateEdition"; private static final Logger LOG = Logger.getInstance("#" + PluginsAdvertiser.class.getName()); private static final String FEATURE_IMPLEMENTATIONS_URL = "http://plugins.jetbrains.com/feature/getImplementations?"; private static final String CASHED_EXTENSIONS = "extensions.xml"; public static final String IDEA_ULTIMATE_EDITION = "IntelliJ IDEA Ultimate Edition"; public static final String ULTIMATE_EDITION_SUGGESTION = "Do not suggest Ultimate Edition"; public static final String CHECK_ULTIMATE_EDITION_TITLE = "Check " + IDEA_ULTIMATE_EDITION; public static final String DISPLAY_ID = "Plugins Suggestion"; public static final NotificationGroup NOTIFICATION_GROUP = new NotificationGroup(DISPLAY_ID, NotificationDisplayType.STICKY_BALLOON, true); private static SoftReference ourKnownExtensions = new SoftReference(null); public static List retrieve(UnknownFeature unknownFeature) { final String featureType = unknownFeature.getFeatureType(); final String implementationName = unknownFeature.getImplementationName(); final String buildNumber = ApplicationInfo.getInstance().getApiVersion(); final String pluginRepositoryUrl = FEATURE_IMPLEMENTATIONS_URL + "featureType=" + featureType + "&implementationName=" + implementationName.replaceAll("#", "%23") + "&build=" + buildNumber; try { HttpURLConnection connection = HttpConfigurable.getInstance().openHttpConnection(pluginRepositoryUrl); connection.connect(); final InputStreamReader streamReader = new InputStreamReader(connection.getInputStream()); try { final JsonReader jsonReader = new JsonReader(streamReader); jsonReader.setLenient(true); final JsonElement jsonRootElement = new JsonParser().parse(jsonReader); final List result = new ArrayList(); for (JsonElement jsonElement : jsonRootElement.getAsJsonArray()) { final JsonObject jsonObject = jsonElement.getAsJsonObject(); final JsonElement pluginId = jsonObject.get("pluginId"); final JsonElement pluginName = jsonObject.get("pluginName"); final JsonElement bundled = jsonObject.get("bundled"); result.add(new Plugin(PluginId.getId(StringUtil.unquoteString(pluginId.toString())), pluginName != null ? StringUtil.unquoteString(pluginName.toString()) : null, Boolean.parseBoolean(StringUtil.unquoteString(bundled.toString())))); } return result; } finally { streamReader.close(); } } catch (IOException e) { LOG.info(e); return null; } } private static Map> loadSupportedExtensions(@NotNull List allPlugins) { final Map availableIds = new HashMap(); for (IdeaPluginDescriptor plugin : allPlugins) { availableIds.put(plugin.getPluginId().getIdString(), plugin); } final String pluginRepositoryUrl = FEATURE_IMPLEMENTATIONS_URL + "featureType=" + FileTypeFactory.FILE_TYPE_FACTORY_EP.getName(); try { HttpURLConnection connection = HttpConfigurable.getInstance().openHttpConnection(pluginRepositoryUrl); connection.connect(); final InputStreamReader streamReader = new InputStreamReader(connection.getInputStream()); try { final JsonReader jsonReader = new JsonReader(streamReader); jsonReader.setLenient(true); final JsonElement jsonRootElement = new JsonParser().parse(jsonReader); final Map> result = new HashMap>(); for (JsonElement jsonElement : jsonRootElement.getAsJsonArray()) { final JsonObject jsonObject = jsonElement.getAsJsonObject(); final String pluginId = StringUtil.unquoteString(jsonObject.get("pluginId").toString()); final JsonElement bundledExt = jsonObject.get("bundled"); boolean isBundled = Boolean.parseBoolean(bundledExt.toString()); final IdeaPluginDescriptor fromServerPluginDescription = availableIds.get(pluginId); if (fromServerPluginDescription == null && !isBundled) continue; final IdeaPluginDescriptor loadedPlugin = PluginManager.getPlugin(PluginId.getId(pluginId)); if (loadedPlugin != null && loadedPlugin.isEnabled()) continue; if (loadedPlugin != null && fromServerPluginDescription != null && StringUtil.compareVersionNumbers(loadedPlugin.getVersion(), fromServerPluginDescription.getVersion()) >= 0) continue; if (fromServerPluginDescription != null && PluginManagerCore.isBrokenPlugin(fromServerPluginDescription)) continue; final JsonElement ext = jsonObject.get("implementationName"); final String extension = StringUtil.unquoteString(ext.toString()); Set pluginIds = result.get(extension); if (pluginIds == null) { pluginIds = new HashSet(); result.put(extension, pluginIds); } final JsonElement pluginNameElement = jsonObject.get("pluginName"); pluginIds.add(new Plugin(PluginId.getId(pluginId), pluginNameElement != null ? StringUtil.unquoteString(pluginNameElement.toString()) : null, isBundled)); } saveExtensions(result); return result; } finally { streamReader.close(); } } catch (Throwable e) { LOG.info(e); return null; } } public static void ensureDeleted() { FileUtil.delete(getExtensionsFile()); } public static KnownExtensions loadExtensions() { KnownExtensions knownExtensions = ourKnownExtensions.get(); if (knownExtensions != null) return knownExtensions; try { File file = getExtensionsFile(); if (file.isFile()) { final Document document = JDOMUtil.loadDocument(file); knownExtensions = XmlSerializer.deserialize(document, KnownExtensions.class); ourKnownExtensions = new SoftReference(knownExtensions); return knownExtensions; } } catch (Exception e) { LOG.info(e); } return null; } private static File getExtensionsFile() { return new File(PathManager.getPluginsPath(), CASHED_EXTENSIONS); } private static void saveExtensions(Map> extensions) throws IOException { File plugins = getExtensionsFile(); if (!plugins.isFile()) { FileUtil.ensureCanCreateFile(plugins); } JDOMUtil.writeDocument(new Document(XmlSerializer.serialize(new KnownExtensions(extensions))), plugins, "\n"); } public static void openDownloadPage() { BrowserUtil.browse(ApplicationInfo.getInstance().getCompanyURL()); } static void enablePlugins(Project project, final Collection disabledPlugins) { final PluginManagerConfigurable managerConfigurable = new PluginManagerConfigurable(PluginManagerUISettings.getInstance()); final PluginManagerMain createPanel = managerConfigurable.getOrCreatePanel(); ShowSettingsUtil.getInstance() .editConfigurable(project, managerConfigurable, new Runnable() { @Override public void run() { final InstalledPluginsTableModel pluginsModel = (InstalledPluginsTableModel)createPanel.getPluginsModel(); final IdeaPluginDescriptor[] descriptors = disabledPlugins.toArray(new IdeaPluginDescriptor[disabledPlugins.size()]); pluginsModel.enableRows(descriptors, Boolean.TRUE); createPanel.getPluginTable().select(descriptors); } }); } @Nullable static IdeaPluginDescriptor getDisabledPlugin(Set plugins) { final List disabledPlugins = PluginManagerCore.getDisabledPlugins(); for (Plugin plugin : plugins) { if (disabledPlugins.contains(plugin.myPluginId)) return PluginManager.getPlugin(PluginId.getId(plugin.myPluginId)); } return null; } static List hasBundledPluginToInstall(Collection plugins) { if (PlatformUtils.isIdeaUltimate()) return null; final List bundled = new ArrayList(); for (Plugin plugin : plugins) { if (plugin.myBundled && PluginManager.getPlugin(PluginId.getId(plugin.myPluginId)) == null) { bundled.add(plugin.myPluginName != null ? plugin.myPluginName : plugin.myPluginId); } } return bundled.isEmpty() ? null : bundled; } @Override public void runActivity(@NotNull final Project project) { if (!UpdateSettings.getInstance().CHECK_NEEDED) return; final UnknownFeaturesCollector collectorSuggester = UnknownFeaturesCollector.getInstance(project); final Set unknownFeatures = collectorSuggester.getUnknownFeatures(); final KnownExtensions extensions = loadExtensions(); if (extensions != null && unknownFeatures.isEmpty()) return; final Runnable runnable = new Runnable() { public void run() { final Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment()) return; ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { private final Set myPlugins = new HashSet(); private List myAllPlugins; private Map myDisabledPlugins = new HashMap(); private List myBundledPlugin; public void run() { try { myAllPlugins = RepositoryHelper.loadPluginsFromRepository(null); if (project.isDisposed()) return; if (extensions == null) { loadSupportedExtensions(myAllPlugins); if (project.isDisposed()) return; EditorNotifications.getInstance(project).updateAllNotifications(); } final Map ids = new HashMap(); for (UnknownFeature feature : unknownFeatures) { ProgressManager.checkCanceled(); final List pluginId = retrieve(feature); if (pluginId != null) { for (Plugin plugin : pluginId) { ids.put(plugin.myPluginId, plugin); } } } final List disabledPlugins = PluginManagerCore.getDisabledPlugins(); //include disabled plugins for (String id : ids.keySet()) { Plugin plugin = ids.get(id); if (disabledPlugins.contains(id)) { final IdeaPluginDescriptor pluginDescriptor = PluginManager.getPlugin(PluginId.getId(id)); if (pluginDescriptor != null) { myDisabledPlugins.put(plugin, pluginDescriptor); } } } myBundledPlugin = hasBundledPluginToInstall(ids.values()); for (IdeaPluginDescriptor loadedPlugin : myAllPlugins) { final PluginId pluginId = loadedPlugin.getPluginId(); if (ids.containsKey(pluginId.getIdString()) && !disabledPlugins.contains(pluginId.getIdString()) && !PluginManagerCore.isBrokenPlugin(loadedPlugin)) { myPlugins.add(PluginDownloader.createDownloader(loadedPlugin)); } } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { onSuccess(); } }, ModalityState.NON_MODAL); } catch (Exception e) { LOG.info(e); } } private void onSuccess() { String message = null; if (!myPlugins.isEmpty() || !myDisabledPlugins.isEmpty()) { message = "Features covered by non-bundled plugins are detected.
"; if (!myDisabledPlugins.isEmpty()) { message += "Enable plugins...
"; } else { message += "Configure plugins...
"; } message += "Ignore All"; } else if (myBundledPlugin != null && !PropertiesComponent.getInstance().isTrueValue(IGNORE_ULTIMATE_EDITION)) { message = "Features covered by " + IDEA_ULTIMATE_EDITION + " (" + StringUtil.join(myBundledPlugin, ", ") + ") are detected.
" + "" + CHECK_ULTIMATE_EDITION_TITLE + "
" + "" + ULTIMATE_EDITION_SUGGESTION + ""; } if (message != null) { final ConfigurePluginsListener notificationListener = new ConfigurePluginsListener(unknownFeatures, project, myAllPlugins, myPlugins, myDisabledPlugins); NOTIFICATION_GROUP.createNotification(DISPLAY_ID, message, NotificationType.INFORMATION, notificationListener).notify(project); } } }); } }; SwingUtilities.invokeLater(runnable); } @Tag("exts") public static class KnownExtensions { @MapAnnotation(surroundWithTag = false, surroundKeyWithTag = false, surroundValueWithTag = false) public Map myExtensions = new HashMap(); public KnownExtensions() { } public KnownExtensions(Map> extensions) { for (String ext : extensions.keySet()) { myExtensions.put(ext, new PluginSet(extensions.get(ext))); } } public Set find(String extension) { final PluginSet pluginSet = myExtensions.get(extension); if (pluginSet != null) { return pluginSet.myPlugins; } return null; } } @Tag("plugins") public static class PluginSet { public Set myPlugins = new HashSet(); public PluginSet() { } public PluginSet(Set plugins) { for (Plugin plugin : plugins) { myPlugins.add(plugin); } } } @Tag("plugin") public static class Plugin implements Comparable { public String myPluginId; public String myPluginName; public boolean myBundled; public Plugin(PluginId pluginId, String pluginName, boolean bundled) { myPluginId = pluginId.getIdString(); myBundled = bundled; myPluginName = pluginName; } public Plugin() { } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Plugin plugin = (Plugin)o; if (myBundled != plugin.myBundled) return false; if (!myPluginId.equals(plugin.myPluginId)) return false; if (myPluginName != null && !myPluginName.equals(plugin.myPluginName)) return false; return true; } @Override public int hashCode() { int result = myPluginId.hashCode(); result = 31 * result + (myBundled ? 1 : 0); result = 31 * result + (myPluginName != null ? myPluginName.hashCode() : 0); return result; } @Override public int compareTo(@NotNull Plugin other) { if (myBundled && !other.myBundled) return -1; if (!myBundled && other.myBundled) return 1; return Comparing.compare(myPluginId, other.myPluginId); } } private static class ConfigurePluginsListener implements NotificationListener { private final Set myUnknownFeatures; private final Project myProject; private final List myAllPlugins; private final Set myPlugins; private final Map myDisabledPlugins; public ConfigurePluginsListener(Set unknownFeatures, Project project, List allPlugins, Set plugins, Map disabledPlugins) { myUnknownFeatures = unknownFeatures; myProject = project; myAllPlugins = allPlugins; myPlugins = plugins; myDisabledPlugins = disabledPlugins; } @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { final String description = event.getDescription(); if ("ignore".equals(description)) { UnknownFeaturesCollector featuresCollector = UnknownFeaturesCollector.getInstance(myProject); for (UnknownFeature feature : myUnknownFeatures) { featuresCollector.ignoreFeature(feature); } notification.expire(); } else if ("configure".equals(description)) { LOG.assertTrue(myAllPlugins != null); notification.expire(); new PluginsAdvertiserDialog(myProject, myPlugins.toArray(new PluginDownloader[myPlugins.size()]), myAllPlugins).show(); } else if ("enable".equals(description)) { enablePlugins(myProject, myDisabledPlugins.values()); notification.expire(); } else if ("ignoreUltimate".equals(description)) { PropertiesComponent.getInstance().setValue(IGNORE_ULTIMATE_EDITION, "true"); notification.expire(); } else if ("open".equals(description)) { openDownloadPage(); notification.expire(); } } } } }