blob: 4ae3caee88e11c7fca268fdd108d88548d23ea38 [file] [log] [blame]
/*
* 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<KnownExtensions> ourKnownExtensions = new SoftReference<KnownExtensions>(null);
public static List<Plugin> 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<Plugin> result = new ArrayList<Plugin>();
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<String, Set<Plugin>> loadSupportedExtensions(@NotNull List<IdeaPluginDescriptor> allPlugins) {
final Map<String, IdeaPluginDescriptor> availableIds = new HashMap<String, IdeaPluginDescriptor>();
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<String, Set<Plugin>> result = new HashMap<String, Set<Plugin>>();
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<Plugin> pluginIds = result.get(extension);
if (pluginIds == null) {
pluginIds = new HashSet<Plugin>();
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>(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<String, Set<Plugin>> 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<IdeaPluginDescriptor> 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<Plugin> plugins) {
final List<String> disabledPlugins = PluginManagerCore.getDisabledPlugins();
for (Plugin plugin : plugins) {
if (disabledPlugins.contains(plugin.myPluginId)) return PluginManager.getPlugin(PluginId.getId(plugin.myPluginId));
}
return null;
}
static List<String> hasBundledPluginToInstall(Collection<Plugin> plugins) {
if (PlatformUtils.isIdeaUltimate()) return null;
final List<String> bundled = new ArrayList<String>();
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<UnknownFeature> 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<PluginDownloader> myPlugins = new HashSet<PluginDownloader>();
private List<IdeaPluginDescriptor> myAllPlugins;
private Map<Plugin, IdeaPluginDescriptor> myDisabledPlugins = new HashMap<Plugin, IdeaPluginDescriptor>();
private List<String> 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<String, Plugin> ids = new HashMap<String, Plugin>();
for (UnknownFeature feature : unknownFeatures) {
ProgressManager.checkCanceled();
final List<Plugin> pluginId = retrieve(feature);
if (pluginId != null) {
for (Plugin plugin : pluginId) {
ids.put(plugin.myPluginId, plugin);
}
}
}
final List<String> 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.<br>";
if (!myDisabledPlugins.isEmpty()) {
message += "<a href=\"enable\">Enable plugins...</a><br>";
}
else {
message += "<a href=\"configure\">Configure plugins...</a><br>";
}
message += "<a href=\"ignore\">Ignore All</a>";
}
else if (myBundledPlugin != null && !PropertiesComponent.getInstance().isTrueValue(IGNORE_ULTIMATE_EDITION)) {
message = "Features covered by " + IDEA_ULTIMATE_EDITION +
" (" + StringUtil.join(myBundledPlugin, ", ") + ") are detected.<br>" +
"<a href=\"open\">" + CHECK_ULTIMATE_EDITION_TITLE + "</a><br>" +
"<a href=\"ignoreUltimate\">" + ULTIMATE_EDITION_SUGGESTION + "</a>";
}
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<String, PluginSet> myExtensions = new HashMap<String, PluginSet>();
public KnownExtensions() {
}
public KnownExtensions(Map<String, Set<Plugin>> extensions) {
for (String ext : extensions.keySet()) {
myExtensions.put(ext, new PluginSet(extensions.get(ext)));
}
}
public Set<Plugin> 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<Plugin> myPlugins = new HashSet<Plugin>();
public PluginSet() {
}
public PluginSet(Set<Plugin> plugins) {
for (Plugin plugin : plugins) {
myPlugins.add(plugin);
}
}
}
@Tag("plugin")
public static class Plugin implements Comparable<Plugin> {
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<UnknownFeature> myUnknownFeatures;
private final Project myProject;
private final List<IdeaPluginDescriptor> myAllPlugins;
private final Set<PluginDownloader> myPlugins;
private final Map<Plugin, IdeaPluginDescriptor> myDisabledPlugins;
public ConfigurePluginsListener(Set<UnknownFeature> unknownFeatures,
Project project,
List<IdeaPluginDescriptor> allPlugins,
Set<PluginDownloader> plugins,
Map<Plugin, IdeaPluginDescriptor> 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();
}
}
}
}
}