blob: f1206ef190f13deaa23f662a8eff13727772aa24 [file] [log] [blame]
/*
* Copyright 2000-2013 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;
import com.google.common.net.HttpHeaders;
import com.intellij.diagnostic.IdeErrorsDialog;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.plugins.*;
import com.intellij.ide.reporter.ConnectionException;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.*;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.PlatformUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.UrlConnectionUtil;
import com.intellij.util.net.HttpConfigurable;
import com.intellij.util.ui.UIUtil;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* See XML file by {@link com.intellij.openapi.application.ex.ApplicationInfoEx#getUpdateUrls()} for reference.
*
* @author mike
* @since Oct 31, 2002
*/
public final class UpdateChecker {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.updateSettings.impl.UpdateChecker");
private static final Map<String, PluginDownloader> ourUpdatedPlugins = new HashMap<String, PluginDownloader>();
public enum DownloadPatchResult {
SUCCESS, FAILED, CANCELED
}
private static final NotNullLazyValue<NotificationGroup> GROUP = new NotNullLazyValue<NotificationGroup>() {
@NotNull
@Override
protected NotificationGroup compute() {
return new NotificationGroup(IdeBundle.message("update.available.group"), NotificationDisplayType.STICKY_BALLOON, true);
}
};
@NonNls private static final String INSTALLATION_UID = "installation.uid";
@NonNls private static final String DISABLED_UPDATE = "disabled_update.txt";
private static Set<String> ourDisabledToUpdatePlugins;
private static Map<String, String> ourAdditionalRequestOptions = new HashMap<String, String>();
private static class Holder {
private static final String UPDATE_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getCheckingUrl();
private static final String PATCHES_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getPatchesUrl();
}
private UpdateChecker() { }
private static String getUpdateUrl() {
String url = System.getProperty("idea.updates.url");
return url != null ? url : Holder.UPDATE_URL;
}
private static String getPatchesUrl() {
String url = System.getProperty("idea.patches.url");
return url != null ? url : Holder.PATCHES_URL;
}
// for scheduled update checks
public static ActionCallback updateAndShowResult() {
final ActionCallback callback = new ActionCallback();
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
doUpdateAndShowResult(null, true, false, UpdateSettings.getInstance(), null, callback);
}
});
return callback;
}
// for manual update checks (Help | Check for Updates)
public static void updateAndShowResult(final @Nullable Project project,
final boolean fromSettings,
final UpdateSettings settings) {
ProgressManager.getInstance().run(new Task.Backgroundable(project, IdeBundle.message("updates.checking.progress"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
doUpdateAndShowResult(project, !fromSettings, true, settings, indicator, null);
}
@Override
public boolean isConditionalModal() {
return fromSettings;
}
@Override
public boolean shouldStartInBackground() {
return !fromSettings;
}
});
}
private static void doUpdateAndShowResult(final @Nullable Project project,
final boolean enableLink,
final boolean manualCheck,
final UpdateSettings updateSettings,
final @Nullable ProgressIndicator indicator,
final @Nullable ActionCallback callback) {
final CheckForUpdateResult result = checkForUpdates(updateSettings);
if (result.getState() == UpdateStrategy.State.LOADED) {
UpdateSettings settings = UpdateSettings.getInstance();
settings.saveLastCheckedInfo();
settings.setKnownChannelIds(result.getAllChannelsIds());
}
else if (result.getState() == UpdateStrategy.State.CONNECTION_ERROR) {
showErrorMessage(manualCheck, IdeBundle.message("updates.error.connection.failed"));
return;
}
final UpdateChannel updatedChannel = result.getUpdatedChannel();
boolean platformUpdate = newChannelReady(result.getChannelToPropose());
BuildNumber buildNumber = null;
if (updatedChannel != null) {
final BuildInfo latestBuild = updatedChannel.getLatestBuild();
if (latestBuild != null) {
buildNumber = latestBuild.getNumber();
}
}
final Collection<IdeaPluginDescriptor> incompatiblePlugins = buildNumber != null ? new HashSet<IdeaPluginDescriptor>() : null;
final Collection<PluginDownloader> updatedPlugins = platformUpdate ? null : updatePlugins(manualCheck, incompatiblePlugins, indicator, buildNumber);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
showUpdateResult(project, result, updatedPlugins, incompatiblePlugins, enableLink, manualCheck);
if (callback != null) {
callback.setDone();
}
}
});
}
public static Collection<PluginDownloader> updatePlugins(boolean manualCheck,
@Nullable Collection<IdeaPluginDescriptor> incompatiblePlugins,
@Nullable ProgressIndicator indicator,
@Nullable BuildNumber buildNumber) {
final Map<PluginId, PluginDownloader> downloaded = new HashMap<PluginId, PluginDownloader>();
final Set<String> failed = new HashSet<String>();
for (String host : getPluginHosts()) {
try {
checkPluginsHost(host, downloaded, incompatiblePlugins, true, indicator, buildNumber);
}
catch (ProcessCanceledException e) {
return null;
}
catch (Exception e) {
LOG.info(e);
failed.add(host);
}
}
final Map<String, IdeaPluginDescriptor> toUpdate = new HashMap<String, IdeaPluginDescriptor>();
final IdeaPluginDescriptor[] installedPlugins = PluginManagerCore.getPlugins();
for (IdeaPluginDescriptor installedPlugin : installedPlugins) {
if (!installedPlugin.isBundled()) {
toUpdate.put(installedPlugin.getPluginId().getIdString(), installedPlugin);
}
}
for (Iterator<PluginId> iterator = downloaded.keySet().iterator(); iterator.hasNext(); ) {
if (!toUpdate.containsKey(iterator.next().getIdString())) {
iterator.remove();
}
}
final File installedTxt = new File(PathManager.getConfigPath(), PluginManager.INSTALLED_TXT);
if (installedTxt.isFile()) {
try {
final String oldInstalledPlugins = FileUtil.loadFile(installedTxt);
for (String pluginId : oldInstalledPlugins.trim().split("\n")) {
if (!toUpdate.containsKey(pluginId)) toUpdate.put(pluginId.trim(), null);
}
}
catch (IOException e) {
LOG.error(e);
}
installedTxt.deleteOnExit();
}
final PluginManagerUISettings updateSettings = PluginManagerUISettings.getInstance();
updateSettings.myOutdatedPlugins.clear();
if (!toUpdate.isEmpty()) {
try {
final List<IdeaPluginDescriptor> process = RepositoryHelper.loadPluginsFromRepository(indicator, buildNumber);
for (IdeaPluginDescriptor loadedPlugin : process) {
final PluginId pluginId = loadedPlugin.getPluginId();
final String idString = pluginId.getIdString();
if (!toUpdate.containsKey(idString)) continue;
if (!downloaded.containsKey(pluginId)) {
prepareToInstall(PluginDownloader.createDownloader(loadedPlugin, buildNumber), buildNumber, downloaded, incompatiblePlugins, true, indicator);
}
}
}
catch (ProcessCanceledException ignore) {
return null;
}
catch (Exception e) {
showErrorMessage(manualCheck, e.getMessage());
}
}
if (!failed.isEmpty()) {
LOG.warn(IdeBundle.message("updates.error.plugin.description.failed", StringUtil.join(failed, ",")));
}
return downloaded.isEmpty() ? null : downloaded.values();
}
private static boolean isReadyToUpdate(String idString, String newVersion) {
final PluginDownloader oldPlugin = ourUpdatedPlugins.get(idString);
return oldPlugin == null || StringUtil.compareVersionNumbers(newVersion, oldPlugin.getPluginVersion()) > 0;
}
private static void prepareToInstall(PluginDownloader downloader,
BuildNumber buildNumber,
Map<PluginId, PluginDownloader> downloaded,
Collection<IdeaPluginDescriptor> incompatiblePlugins,
boolean collectToUpdate,
ProgressIndicator indicator) throws IOException {
final String pluginId = downloader.getPluginId();
final String pluginVersion = downloader.getPluginVersion();
if (collectToUpdate && PluginManagerCore.getDisabledPlugins().contains(pluginId)) return;
final IdeaPluginDescriptor installedPlugin = PluginManager.getPlugin(PluginId.getId(pluginId));
if (installedPlugin == null || pluginVersion == null ||
PluginDownloader.compareVersionsSkipBroken(installedPlugin, pluginVersion) > 0) {
IdeaPluginDescriptor descriptor = null;
if (isReadyToUpdate(pluginId, pluginVersion)) {
descriptor = downloader.getDescriptor();
if (descriptor == null) {
if (downloader.prepareToInstall(indicator, buildNumber)) {
descriptor = downloader.getDescriptor();
}
ourUpdatedPlugins.put(pluginId, downloader);
}
} else {
final PluginDownloader oldDownloader = ourUpdatedPlugins.get(pluginId);
if (oldDownloader != null) {
downloader = oldDownloader;
descriptor = oldDownloader.getDescriptor();
}
}
if (descriptor != null &&
!PluginManagerCore.isIncompatible(descriptor, buildNumber) &&
!InstalledPluginsTableModel.wasUpdated(descriptor.getPluginId())) {
downloaded.put(PluginId.getId(pluginId), downloader);
}
}
//collect plugins which were not updated and would be incompatible with new version
if (incompatiblePlugins != null &&
installedPlugin != null && installedPlugin.isEnabled() && !downloaded.containsKey(installedPlugin.getPluginId()) &&
PluginManagerCore.isIncompatible(installedPlugin, buildNumber)) {
incompatiblePlugins.add(installedPlugin);
}
}
private static void showErrorMessage(boolean showDialog, final String message) {
if (showDialog) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(message, IdeBundle.message("updates.error.connection.title"));
}
});
}
else {
LOG.warn(message);
}
}
private static List<String> getPluginHosts() {
ArrayList<String> hosts = new ArrayList<String>();
hosts.addAll(UpdateSettings.getInstance().myPluginHosts);
ContainerUtil.addIfNotNull(ApplicationInfoEx.getInstanceEx().getBuiltinPluginsUrl(), hosts);
final String pluginHosts = System.getProperty("idea.plugin.hosts");
if (pluginHosts != null) {
ContainerUtil.addAll(hosts, pluginHosts.split(";"));
}
return hosts;
}
public static boolean checkPluginsHost(final String host, final Map<PluginId, PluginDownloader> downloaded) throws Exception {
try {
return checkPluginsHost(host, downloaded, null, true, null, null);
}
catch (ProcessCanceledException e) {
return false;
}
}
public static boolean checkPluginsHost(final String host,
final Map<PluginId, PluginDownloader> downloaded,
final boolean collectToUpdate,
@Nullable ProgressIndicator indicator) throws Exception {
return checkPluginsHost(host, downloaded, null, collectToUpdate, indicator, null);
}
private static boolean checkPluginsHost(final String host,
final Map<PluginId, PluginDownloader> downloaded,
final @Nullable Collection<IdeaPluginDescriptor> incompatiblePlugins,
final boolean collectToUpdate,
final @Nullable ProgressIndicator indicator,
final BuildNumber buildNumber) throws Exception {
InputStream inputStream = loadVersionInfo(host);
if (inputStream == null) return false;
final Document document;
try {
document = JDOMUtil.loadDocument(inputStream);
}
catch (JDOMException e) {
return false;
}
inputStream = loadVersionInfo(host);
if (inputStream == null) return false;
final List<IdeaPluginDescriptor> descriptors = RepositoryHelper.loadPluginsFromDescription(inputStream, indicator);
for (IdeaPluginDescriptor descriptor : descriptors) {
((PluginNode)descriptor).setRepositoryName(host);
prepareToInstall(PluginDownloader.createDownloader(descriptor, buildNumber), buildNumber, downloaded, incompatiblePlugins, collectToUpdate,
indicator);
}
boolean success = true;
for (Object plugin : document.getRootElement().getChildren("plugin")) {
final Element pluginElement = (Element)plugin;
final String pluginId = pluginElement.getAttributeValue("id");
final String pluginUrl = pluginElement.getAttributeValue("url");
final String pluginVersion = pluginElement.getAttributeValue("version");
final Element descriptionElement = pluginElement.getChild("description");
final String description;
if (descriptionElement != null) {
description = descriptionElement.getText();
} else {
description = null;
}
final List<PluginId> dependsPlugins = new ArrayList<PluginId>();
final List depends = pluginElement.getChildren("depends");
for (Object depend : depends) {
dependsPlugins.add(PluginId.getId(((Element)depend).getText()));
}
if (pluginId == null) {
LOG.info("plugin id should not be null");
success = false;
continue;
}
if (pluginUrl == null) {
LOG.info("plugin url should not be null");
success = false;
continue;
}
final VirtualFile pluginFile = PluginDownloader.findPluginFile(pluginUrl, host);
if (pluginFile == null) continue;
if (collectToUpdate) {
final String finalPluginUrl = getPluginUrl(pluginFile);
final Runnable updatePluginRunnable = new Runnable() {
public void run() {
try {
final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
if (progressIndicator != null) {
progressIndicator.setText2(finalPluginUrl);
}
final PluginDownloader downloader = new PluginDownloader(pluginId, finalPluginUrl, pluginVersion, null, null, buildNumber);
prepareToInstall(downloader, buildNumber, downloaded, incompatiblePlugins, collectToUpdate, indicator);
}
catch (IOException e) {
LOG.info(e);
}
}
};
if (ApplicationManager.getApplication().isDispatchThread()) {
String title = IdeBundle.message("update.uploading.plugin.progress.title");
ProgressManager.getInstance().runProcessWithProgressSynchronously(updatePluginRunnable, title, true, null);
}
else {
updatePluginRunnable.run();
}
} else {
final PluginDownloader downloader = new PluginDownloader(pluginId, pluginUrl, pluginVersion);
downloader.setDescription(description);
downloader.setDepends(dependsPlugins);
downloaded.put(PluginId.getId(pluginId), downloader);
}
}
return success;
}
@NotNull
private static String getPluginUrl(@NotNull VirtualFile pluginFile) {
String protocol = pluginFile.getFileSystem().getProtocol();
if (StandardFileSystems.FILE_PROTOCOL.equals(protocol) && SystemInfo.isWindows) {
String path = pluginFile.getPath();
if (path.length() != 0 && path.charAt(0) != '/') {
return protocol + ":///" + path; // fix file URI on Windows
}
}
return pluginFile.getUrl();
}
@NotNull
private static CheckForUpdateResult checkForUpdates(final UpdateSettings settings) {
UpdatesInfo info;
try {
UpdatesXmlLoader loader = new UpdatesXmlLoader(getUpdateUrl());
info = loader.loadUpdatesInfo();
if (info == null) {
return new CheckForUpdateResult(UpdateStrategy.State.NOTHING_LOADED);
}
}
catch (ConnectionException e) {
return new CheckForUpdateResult(UpdateStrategy.State.CONNECTION_ERROR, e);
}
ApplicationInfo appInfo = ApplicationInfo.getInstance();
int majorVersion = Integer.parseInt(appInfo.getMajorVersion());
UpdateStrategy strategy = new UpdateStrategy(majorVersion, appInfo.getBuild(), info, settings);
return strategy.checkForUpdates();
}
public static void addUpdateRequestParameter(String name) {
addUpdateRequestParameter(name, "");
}
public static void addUpdateRequestParameter(@NotNull String name, @NotNull String value) {
ourAdditionalRequestOptions.put(name, value);
}
@Contract("null -> false")
private static boolean newChannelReady(@Nullable UpdateChannel channelToPropose) {
return channelToPropose != null && channelToPropose.getLatestBuild() != null;
}
@SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
private static void showUpdateResult(@Nullable final Project project,
final CheckForUpdateResult checkForUpdateResult,
final Collection<PluginDownloader> updatedPlugins,
final Collection<IdeaPluginDescriptor> incompatiblePlugins,
final boolean enableLink,
final boolean alwaysShowResults) {
final UpdateChannel channelToPropose = checkForUpdateResult.getChannelToPropose();
final UpdateChannel updatedChannel = checkForUpdateResult.getUpdatedChannel();
if (newChannelReady(channelToPropose)) {
Runnable runnable = new Runnable() {
public void run() {
new NewChannelDialog(channelToPropose).show();
}
};
if (alwaysShowResults) {
runnable.run();
}
else {
String message = IdeBundle.message("updates.new.version.available", ApplicationNamesInfo.getInstance().getFullProductName());
showNotification(project, message, false, runnable);
}
}
else if (updatedChannel != null) {
Runnable runnable = new Runnable() {
@Override
public void run() {
new UpdateInfoDialog(updatedChannel, enableLink, updatedPlugins, incompatiblePlugins).show();
}
};
if (alwaysShowResults) {
runnable.run();
}
else {
String message = IdeBundle.message("updates.ready.message", ApplicationNamesInfo.getInstance().getFullProductName());
showNotification(project, message, false, runnable);
}
}
else if (updatedPlugins != null && !updatedPlugins.isEmpty()) {
Runnable runnable = new Runnable() {
@Override
public void run() {
new PluginUpdateInfoDialog(updatedPlugins, enableLink).show();
}
};
if (alwaysShowResults) {
runnable.run();
}
else {
String plugins = StringUtil.join(updatedPlugins, new Function<PluginDownloader, String>() {
@Override
public String fun(PluginDownloader downloader) {
return downloader.getPluginName();
}
}, ", ");
String message = IdeBundle.message("updates.plugins.ready.message", updatedPlugins.size(), plugins);
showNotification(project, message, false, runnable);
}
}
else if (alwaysShowResults) {
new NoUpdatesDialog(enableLink).show();
}
}
private static void showNotification(@Nullable Project project, String message, boolean error, final @Nullable Runnable runnable) {
NotificationListener listener = null;
if (runnable != null) {
listener = new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
notification.expire();
runnable.run();
}
};
}
String title = IdeBundle.message("updates.info.dialog.title");
NotificationType type = error ? NotificationType.ERROR : NotificationType.INFORMATION;
Notifications.Bus.notify(GROUP.getValue().createNotification(title, message, type, listener), project);
}
public static String prepareUpdateCheckArgs() {
addUpdateRequestParameter("build", ApplicationInfo.getInstance().getBuild().asString());
addUpdateRequestParameter("uid", getInstallationUID(PropertiesComponent.getInstance()));
addUpdateRequestParameter("os", SystemInfo.OS_NAME + ' ' + SystemInfo.OS_VERSION);
if (ApplicationInfoEx.getInstanceEx().isEAP()) {
addUpdateRequestParameter("eap");
}
StringBuilder args = new StringBuilder();
try {
for (String name : ourAdditionalRequestOptions.keySet()) {
if (args.length() > 0) {
args.append('&');
}
args.append(URLEncoder.encode(name, "UTF-8"));
String value = ourAdditionalRequestOptions.get(name);
if (!StringUtil.isEmpty(value)) {
args.append('=').append(URLEncoder.encode(value, "UTF-8"));
}
}
return args.toString();
}
catch (UnsupportedEncodingException e) {
return ""; // Can't be anyway
}
}
private static InputStream loadVersionInfo(final String url) throws Exception {
final InputStream[] inputStreams = new InputStream[]{null};
final Exception[] exception = new Exception[]{null};
Future<?> downloadThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
public void run() {
try {
final String urlToCheck;
if (!StandardFileSystems.FILE_PROTOCOL.equals(new URL(url).getProtocol())) {
urlToCheck = url + (url.contains("?") ? "&" : "?") + "build=" + ApplicationInfo.getInstance().getBuild().asString();
} else {
urlToCheck = url;
}
HttpURLConnection connection;
if (ApplicationManager.getApplication() != null) {
connection = HttpConfigurable.getInstance().openHttpConnection(urlToCheck);
}
else {
connection = (HttpURLConnection)new URL(urlToCheck).openConnection();
connection.setReadTimeout(HttpConfigurable.CONNECTION_TIMEOUT);
connection.setConnectTimeout(HttpConfigurable.CONNECTION_TIMEOUT);
}
connection.connect();
inputStreams[0] = connection.getInputStream();
}
catch (IOException e) {
exception[0] = e;
}
}
});
try {
downloadThreadFuture.get(5, TimeUnit.SECONDS);
}
catch (TimeoutException ignored) { }
if (!downloadThreadFuture.isDone()) {
downloadThreadFuture.cancel(true);
throw new ConnectionException(IdeBundle.message("updates.timeout.error"));
}
if (exception[0] != null) throw exception[0];
return inputStreams[0];
}
public static String getInstallationUID(final PropertiesComponent propertiesComponent) {
if (SystemInfo.isWindows) {
String uid = getInstallationUIDOnWindows(propertiesComponent);
if (uid != null) {
return uid;
}
}
String uid;
if (!propertiesComponent.isValueSet(INSTALLATION_UID)) {
uid = generateUUID();
propertiesComponent.setValue(INSTALLATION_UID, uid);
}
else {
uid = propertiesComponent.getValue(INSTALLATION_UID);
}
return uid;
}
@Nullable
private static String getInstallationUIDOnWindows(PropertiesComponent propertiesComponent) {
String appdata = System.getenv("APPDATA");
if (appdata != null) {
File jetBrainsDir = new File(appdata, "JetBrains");
if (jetBrainsDir.exists() || jetBrainsDir.mkdirs()) {
File permanentIdFile = new File(jetBrainsDir, "PermanentUserId");
try {
if (permanentIdFile.exists()) {
return FileUtil.loadFile(permanentIdFile).trim();
}
String uuid;
if (propertiesComponent.isValueSet(INSTALLATION_UID)) {
uuid = propertiesComponent.getValue(INSTALLATION_UID);
}
else {
uuid = generateUUID();
}
FileUtil.writeToFile(permanentIdFile, uuid);
return uuid;
}
catch (IOException ignored) { }
}
}
return null;
}
private static String generateUUID() {
try {
return UUID.randomUUID().toString();
}
catch (Exception ignored) { }
catch (InternalError ignored) { }
return "";
}
public static boolean install(Collection<PluginDownloader> downloaders) {
boolean installed = false;
for (PluginDownloader downloader : downloaders) {
if (getDisabledToUpdatePlugins().contains(downloader.getPluginId())) continue;
try {
if (downloader.prepareToInstall(ProgressManager.getInstance().getProgressIndicator())) {
final IdeaPluginDescriptor descriptor = downloader.getDescriptor();
if (descriptor != null) {
InstalledPluginsTableModel.updateExistingPlugin(descriptor, PluginManager.getPlugin(descriptor.getPluginId()));
downloader.install();
installed = true;
}
}
}
catch (IOException e) {
LOG.info(e);
}
}
return installed;
}
public static DownloadPatchResult downloadAndInstallPatch(final BuildInfo newVersion) {
final DownloadPatchResult[] result = new DownloadPatchResult[]{DownloadPatchResult.CANCELED};
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
public void run() {
try {
doDownloadAndInstallPatch(newVersion, ProgressManager.getInstance().getProgressIndicator());
result[0] = DownloadPatchResult.SUCCESS;
}
catch (final IOException e) {
LOG.info(e);
result[0] = DownloadPatchResult.FAILED;
Notifications.Bus.notify(new Notification("Updater", "Failed to download patch file", e.getMessage(), NotificationType.ERROR));
}
}
}, IdeBundle.message("update.downloading.patch.progress.title"), true, null)) {
return DownloadPatchResult.CANCELED;
}
return result[0];
}
private static void doDownloadAndInstallPatch(BuildInfo newVersion, ProgressIndicator i) throws IOException {
PatchInfo patch = newVersion.findPatchForCurrentBuild();
if (patch == null) throw new IOException("No patch is available for current version");
String productCode = ApplicationInfo.getInstance().getBuild().getProductCode();
String osSuffix = "-" + patch.getOSSuffix();
String fromBuildNumber = patch.getFromBuild().asStringWithoutProductCode();
String toBuildNumber = newVersion.getNumber().asStringWithoutProductCode();
String bundledJdk = "jdk-bundled".equals(System.getProperty("idea.java.redist")) ? "-jdk-bundled" : "";
String fileName = productCode + "-" + fromBuildNumber + "-" + toBuildNumber + "-patch" + bundledJdk + osSuffix + ".jar";
String platform = PlatformUtils.getPlatformPrefix();
File tempFile = FileUtil.createTempFile(platform, "patch", true);
LOG.info(String.format("[Patch] Download %s to %s", fileName, tempFile.getAbsolutePath()));
OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
try {
String requestUrl = new URL(new URL(getPatchesUrl()), fileName).toString();
URLConnection connection;
int followCount = 2;
while(true) {
connection = HttpConfigurable.getInstance().openConnection(requestUrl);
if (connection instanceof HttpURLConnection) {
HttpURLConnection hcnx = (HttpURLConnection)connection;
int code = hcnx.getResponseCode();
if (code >= 301 && code <= 307 && --followCount >= 0) {
String loc = hcnx.getHeaderField(HttpHeaders.LOCATION);
if (loc != null) {
requestUrl = loc;
continue;
}
}
}
break;
}
try {
InputStream in = UrlConnectionUtil.getConnectionInputStreamWithException(connection, i);
try {
int total = connection.getContentLength();
i.setIndeterminate(total <= 0);
byte[] buffer = new byte[10 * 1024];
int count;
int read = 0;
while ((count = in.read(buffer)) > 0) {
i.checkCanceled();
out.write(buffer, 0, count);
read += count;
if (total > 0) {
i.setFraction(((double)read) / total);
i.setText2((read / 1024) + "/" + (total / 1024) + " KB");
}
else {
i.setText2((read / 1024) + " KB");
}
}
}
finally {
in.close();
}
}
finally {
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection).disconnect();
}
}
}
finally {
out.close();
}
String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase();
File patchFile = new File(FileUtil.getTempDirectory(), patchFileName);
FileUtil.copy(tempFile, patchFile);
FileUtil.delete(tempFile);
LOG.info(String.format("[Patch] moved to %s", patchFile.getAbsolutePath()));
}
public static Set<String> getDisabledToUpdatePlugins() {
if (ourDisabledToUpdatePlugins == null) {
ourDisabledToUpdatePlugins = new TreeSet<String>();
if (!ApplicationManager.getApplication().isUnitTestMode()) {
try {
final File file = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
if (file.isFile()) {
final String[] ids = FileUtil.loadFile(file).split("[\\s]");
for (String id : ids) {
if (id != null && id.trim().length() > 0) {
ourDisabledToUpdatePlugins.add(id.trim());
}
}
}
}
catch (IOException e) {
LOG.error(e);
}
}
}
return ourDisabledToUpdatePlugins;
}
public static void saveDisabledToUpdatePlugins() {
final File plugins = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
try {
PluginManagerCore.savePluginsList(getDisabledToUpdatePlugins(), false, plugins);
}
catch (IOException e) {
LOG.error(e);
}
}
private static boolean ourHasFailedPlugins = false;
public static void checkForUpdate(IdeaLoggingEvent event) {
if (!ourHasFailedPlugins && UpdateSettings.getInstance().CHECK_NEEDED) {
final Throwable throwable = event.getThrowable();
final IdeaPluginDescriptor pluginDescriptor = PluginManager.getPlugin(IdeErrorsDialog.findPluginId(throwable));
if (pluginDescriptor != null && !pluginDescriptor.isBundled()) {
ourHasFailedPlugins = true;
updateAndShowResult();
}
}
}
static String getDownloadUrl(IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) throws UnsupportedEncodingException {
String url = null;
if (descriptor instanceof PluginNode) {
url = ((PluginNode)descriptor).getDownloadUrl();
if (url != null) {
final String repositoryName = ((PluginNode)descriptor).getRepositoryName();
if (repositoryName != null) {
final VirtualFile pluginFile = PluginDownloader.findPluginFile(url, repositoryName);
if (pluginFile != null) {
url = getPluginUrl(pluginFile);
}
}
}
}
if (url == null) {
String uuid = ApplicationManager.getApplication() == null ?
UUID.randomUUID().toString() :
getInstallationUID(PropertiesComponent.getInstance());
String buildNumberAsString = buildNumber != null
? buildNumber.asString()
: ApplicationManager.getApplication() != null
? ApplicationInfo.getInstance().getApiVersion()
: ApplicationInfoImpl.getShadowInstance().getBuild().asString();
url = RepositoryHelper.getDownloadUrl() + URLEncoder.encode(descriptor.getPluginId().getIdString(), "UTF8") +
"&build=" + buildNumberAsString + "&uuid=" + URLEncoder.encode(uuid, "UTF8");
}
return url;
}
}