2 * Copyright 2000-2011 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.updateSettings.impl;
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.ide.plugins.*;
20 import com.intellij.ide.reporter.ConnectionException;
21 import com.intellij.ide.util.PropertiesComponent;
22 import com.intellij.notification.Notification;
23 import com.intellij.notification.NotificationType;
24 import com.intellij.notification.Notifications;
25 import com.intellij.openapi.application.ApplicationInfo;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.PathManager;
28 import com.intellij.openapi.application.ex.ApplicationInfoEx;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.extensions.PluginId;
31 import com.intellij.openapi.progress.ProcessCanceledException;
32 import com.intellij.openapi.progress.ProgressIndicator;
33 import com.intellij.openapi.progress.ProgressManager;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.openapi.util.BuildNumber;
36 import com.intellij.openapi.util.JDOMUtil;
37 import com.intellij.openapi.util.SystemInfo;
38 import com.intellij.openapi.util.io.FileUtil;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.util.PlatformUtils;
42 import com.intellij.util.containers.ContainerUtil;
43 import com.intellij.util.io.UrlConnectionUtil;
44 import com.intellij.util.net.HttpConfigurable;
45 import com.intellij.util.text.DateFormatUtil;
46 import com.intellij.util.ui.UIUtil;
47 import org.jdom.Document;
48 import org.jdom.Element;
49 import org.jdom.JDOMException;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
56 import java.net.HttpURLConnection;
58 import java.net.URLConnection;
60 import java.util.concurrent.Future;
61 import java.util.concurrent.TimeUnit;
62 import java.util.concurrent.TimeoutException;
69 * <version>4.5.2</version>
70 * <title>New Intellij IDEA Version</title>
72 * New version of IntelliJ IDEA is available.
73 * Please visit http://www.intellij.com/ for more info.
81 public final class UpdateChecker {
82 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.updateSettings.impl.UpdateChecker");
84 public static String ADDITIONAL_REQUEST_OPTIONS = "";
85 @NonNls private static final String INSTALLATION_UID = "installation.uid";
87 private UpdateChecker() {
90 public static void showConnectionErrorDialog() {
91 Messages.showErrorDialog(IdeBundle.message("error.checkforupdates.connection.failed"),
92 IdeBundle.message("title.connection.error"));
95 public static enum DownloadPatchResult {
96 SUCCESS, FAILED, CANCELED
99 private static boolean myVeryFirstOpening = true;
103 private static final String DISABLED_UPDATE = "disabled_update.txt";
104 private static TreeSet<String> ourDisabledToUpdatePlugins;
106 private static class StringHolder {
107 private static final String UPDATE_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getCheckingUrl();
108 private static final String PATCHES_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getPatchesUrl();
109 private StringHolder() { }
112 private static String getUpdateUrl() {
113 String url = System.getProperty("idea.updates.url");
117 return StringHolder.UPDATE_URL;
120 private static String getPatchesUrl() {
121 String url = System.getProperty("idea.patches.url");
125 return StringHolder.PATCHES_URL;
128 public static boolean isMyVeryFirstOpening() {
129 return myVeryFirstOpening;
132 public static void setMyVeryFirstOpening(final boolean myVeryFirstProjectOpening) {
133 myVeryFirstOpening = myVeryFirstProjectOpening;
136 public static boolean checkNeeded() {
137 final UpdateSettings settings = UpdateSettings.getInstance();
138 if (settings == null || getUpdateUrl() == null) return false;
140 final long timeDelta = System.currentTimeMillis() - settings.LAST_TIME_CHECKED;
141 if (Math.abs(timeDelta) < DateFormatUtil.DAY) return false;
143 return settings.CHECK_NEEDED;
146 public static List<PluginDownloader> updatePlugins(final boolean showErrorDialog,
147 final @Nullable PluginHostsConfigurable hostsConfigurable) {
148 final List<PluginDownloader> downloaded = new ArrayList<PluginDownloader>();
149 final Set<String> failed = new HashSet<String>();
150 for (String host : getPluginHosts(hostsConfigurable)) {
152 checkPluginsHost(host, downloaded);
154 catch (Exception e) {
160 final Map<String, IdeaPluginDescriptor> toUpdate = new HashMap<String, IdeaPluginDescriptor>();
161 final IdeaPluginDescriptor[] installedPlugins = PluginManager.getPlugins();
162 for (IdeaPluginDescriptor installedPlugin : installedPlugins) {
163 if (!installedPlugin.isBundled()) {
164 toUpdate.put(installedPlugin.getPluginId().getIdString(), installedPlugin);
167 final PluginManagerUISettings updateSettings = PluginManagerUISettings.getInstance();
168 updateSettings.myOutdatedPlugins.clear();
169 if (!toUpdate.isEmpty()) {
171 final ArrayList<IdeaPluginDescriptor> process = RepositoryHelper.process(null);
172 for (IdeaPluginDescriptor loadedPlugin : process) {
173 final String idString = loadedPlugin.getPluginId().getIdString();
174 final IdeaPluginDescriptor installedPlugin = toUpdate.get(idString);
175 if (installedPlugin != null) {
176 if (StringUtil.compareVersionNumbers(loadedPlugin.getVersion(), installedPlugin.getVersion()) > 0) {
177 updateSettings.myOutdatedPlugins.add(idString);
178 if (((IdeaPluginDescriptorImpl)installedPlugin).isEnabled()) {
179 final PluginDownloader downloader = PluginDownloader.createDownloader(loadedPlugin);
180 if (downloader.prepareToInstall()) {
181 downloaded.add(downloader);
188 catch (Exception e) {
189 showErrorMessage(showErrorDialog, e.getMessage());
193 if (!failed.isEmpty()) {
194 showErrorMessage(showErrorDialog, IdeBundle.message("connection.failed.message", StringUtil.join(failed, ",")));
196 return downloaded.isEmpty() ? null : downloaded;
199 private static void showErrorMessage(boolean showErrorDialog, final String failedMessage) {
200 if (showErrorDialog) {
201 UIUtil.invokeLaterIfNeeded(new Runnable() {
204 Messages.showErrorDialog(failedMessage, IdeBundle.message("title.connection.error"));
209 LOG.info(failedMessage);
213 private static List<String> getPluginHosts(@Nullable PluginHostsConfigurable hostsConfigurable) {
214 final ArrayList<String> hosts = new ArrayList<String>();
215 if (hostsConfigurable != null) {
216 hosts.addAll(hostsConfigurable.getPluginsHosts());
219 hosts.addAll(UpdateSettings.getInstance().myPluginHosts);
221 final String pluginHosts = System.getProperty("idea.plugin.hosts");
222 if (pluginHosts != null) {
223 ContainerUtil.addAll(hosts, pluginHosts.split(";"));
228 public static boolean checkPluginsHost(final String host, final List<PluginDownloader> downloaded) throws Exception {
229 return checkPluginsHost(host, downloaded, true);
232 public static boolean checkPluginsHost(final String host,
233 final List<PluginDownloader> downloaded,
234 final boolean collectToUpdate) throws Exception {
235 InputStream inputStream = loadVersionInfo(host);
236 if (inputStream == null) return false;
237 final Document document;
239 document = JDOMUtil.loadDocument(inputStream);
241 catch (JDOMException e) {
245 inputStream = loadVersionInfo(host);
246 if (inputStream == null) return false;
247 final ArrayList<IdeaPluginDescriptor> descriptors = RepositoryHelper.loadPluginsFromDescription(inputStream);
248 for (IdeaPluginDescriptor descriptor : descriptors) {
249 downloaded.add(PluginDownloader.createDownloader(descriptor));
252 boolean success = true;
253 for (Object plugin : document.getRootElement().getChildren("plugin")) {
254 final Element pluginElement = (Element)plugin;
255 final String pluginId = pluginElement.getAttributeValue("id");
256 final String pluginUrl = pluginElement.getAttributeValue("url");
257 final String pluginVersion = pluginElement.getAttributeValue("version");
258 final Element descriptionElement = pluginElement.getChild("description");
259 final String description;
260 if (descriptionElement != null) {
261 description = descriptionElement.getText();
266 final List<PluginId> dependsPlugins = new ArrayList<PluginId>();
267 final List depends = pluginElement.getChildren("depends");
268 for (Object depend : depends) {
269 dependsPlugins.add(PluginId.getId(((Element)depend).getText()));
272 if (pluginId == null) {
273 LOG.info("plugin id should not be null");
278 if (pluginUrl == null) {
279 LOG.info("plugin url should not be null");
284 final VirtualFile pluginFile = PluginDownloader.findPluginFile(pluginUrl, host);
285 if (pluginFile == null) continue;
287 if (collectToUpdate) {
288 final String finalPluginUrl = pluginFile.getUrl();
289 final Runnable updatePluginRunnable = new Runnable() {
292 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
293 if (progressIndicator != null) {
294 progressIndicator.setText(finalPluginUrl);
296 final PluginDownloader uploader = new PluginDownloader(pluginId, finalPluginUrl, pluginVersion);
297 if (uploader.prepareToInstall()) {
298 downloaded.add(uploader);
301 catch (IOException e) {
306 if (ApplicationManager.getApplication().isDispatchThread()) {
307 ProgressManager.getInstance().runProcessWithProgressSynchronously(updatePluginRunnable, IdeBundle.message("update.uploading.plugin.progress.title"), true, null);
310 updatePluginRunnable.run();
313 final PluginDownloader downloader = new PluginDownloader(pluginId, pluginUrl, pluginVersion);
314 downloader.setDescription(description);
315 downloader.setDepends(dependsPlugins);
316 downloaded.add(downloader);
323 public static CheckForUpdateResult doCheckForUpdates(final UpdateSettings settings) {
324 ApplicationInfo appInfo = ApplicationInfo.getInstance();
325 BuildNumber currentBuild = appInfo.getBuild();
326 int majorVersion = Integer.parseInt(appInfo.getMajorVersion());
327 final UpdatesXmlLoader loader = new UpdatesXmlLoader(getUpdateUrl(), getInstallationUID(), null);
328 final UpdatesInfo info;
330 info = loader.loadUpdatesInfo();
332 return new CheckForUpdateResult(UpdateStrategy.State.NOTHING_LOADED);
335 catch (ConnectionException e) {
336 return new CheckForUpdateResult(UpdateStrategy.State.CONNECTION_ERROR, e);
339 UpdateStrategy strategy = new UpdateStrategy(majorVersion, currentBuild, info, settings);
340 return strategy.checkForUpdates();
345 public static CheckForUpdateResult checkForUpdates() {
346 return checkForUpdates(false);
350 public static CheckForUpdateResult checkForUpdates(final boolean disregardIgnoredBuilds) {
351 if (LOG.isDebugEnabled()) {
352 LOG.debug("enter: auto checkForUpdates()");
355 UserUpdateSettings settings = UpdateSettings.getInstance();
356 if (disregardIgnoredBuilds) {
357 settings = new UserUpdateSettings() {
360 public List<String> getKnownChannelsIds() {
361 return UpdateSettings.getInstance().getKnownChannelsIds();
365 public List<String> getIgnoredBuildNumbers() {
366 return Collections.emptyList();
370 public void setKnownChannelIds(List<String> ids) {
371 UpdateSettings.getInstance().setKnownChannelIds(ids);
376 public ChannelStatus getSelectedChannelStatus() {
377 return UpdateSettings.getInstance().getSelectedChannelStatus();
382 final CheckForUpdateResult result = doCheckForUpdates(UpdateSettings.getInstance());
384 if (result.getState() == UpdateStrategy.State.LOADED) {
385 UpdateSettings.getInstance().LAST_TIME_CHECKED = System.currentTimeMillis();
386 settings.setKnownChannelIds(result.getAllChannelsIds());
392 public static void showUpdateResult(CheckForUpdateResult checkForUpdateResult,
393 List<PluginDownloader> updatedPlugins,
394 boolean showConfirmation,
396 final boolean alwaysShowResults) {
397 UpdateChannel channelToPropose = checkForUpdateResult.getChannelToPropose();
398 if (channelToPropose != null && channelToPropose.getLatestBuild() != null) {
399 NewChannelDialog dialog = new NewChannelDialog(channelToPropose);
400 dialog.setModal(alwaysShowResults);
403 else if (checkForUpdateResult.hasNewBuildInSelectedChannel()) {
404 UpdateInfoDialog dialog = new UpdateInfoDialog(true, checkForUpdateResult.getUpdatedChannel(), updatedPlugins, enableLink);
405 dialog.setModal(alwaysShowResults);
408 else if (updatedPlugins != null || alwaysShowResults) {
409 NoUpdatesDialog dialog = new NoUpdatesDialog(true, updatedPlugins, enableLink);
410 dialog.setShowConfirmation(showConfirmation);
415 private static InputStream loadVersionInfo(final String url) throws Exception {
416 if (LOG.isDebugEnabled()) {
417 LOG.debug("enter: loadVersionInfo(UPDATE_URL='" + url + "' )");
419 final InputStream[] inputStreams = new InputStream[]{null};
420 final Exception[] exception = new Exception[]{null};
421 Future<?> downloadThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
424 HttpConfigurable.getInstance().prepareURL(url);
426 String uid = getInstallationUID();
428 final URL requestUrl =
429 new URL(url + "?build=" + ApplicationInfo.getInstance().getBuild().asString() + "&uid=" + uid + ADDITIONAL_REQUEST_OPTIONS);
430 inputStreams[0] = requestUrl.openStream();
432 catch (IOException e) {
439 downloadThreadFuture.get(5, TimeUnit.SECONDS);
441 catch (TimeoutException e) {
445 if (!downloadThreadFuture.isDone()) {
446 downloadThreadFuture.cancel(true);
447 throw new ConnectionException(IdeBundle.message("updates.timeout.error"));
450 if (exception[0] != null) throw exception[0];
451 return inputStreams[0];
454 public static String getInstallationUID() {
455 final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
457 if (!propertiesComponent.isValueSet(INSTALLATION_UID)) {
459 uid = UUID.randomUUID().toString();
461 catch (Exception ignored) {
463 catch (InternalError ignored) {
465 propertiesComponent.setValue(INSTALLATION_UID, uid);
468 uid = propertiesComponent.getValue(INSTALLATION_UID);
473 public static boolean install(List<PluginDownloader> downloaders) {
474 boolean installed = false;
475 for (PluginDownloader downloader : downloaders) {
476 if (getDisabledToUpdatePlugins().contains(downloader.getPluginId())) continue;
478 downloader.install();
481 catch (IOException e) {
489 public static DownloadPatchResult downloadAndInstallPatch(final BuildInfo newVersion) {
490 final DownloadPatchResult[] result = new DownloadPatchResult[]{DownloadPatchResult.CANCELED};
492 if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
495 doDownloadAndInstallPatch(newVersion, ProgressManager.getInstance().getProgressIndicator());
496 result[0] = DownloadPatchResult.SUCCESS;
498 catch (final IOException e) {
500 result[0] = DownloadPatchResult.FAILED;
502 SwingUtilities.invokeLater(new Runnable() {
504 Notifications.Bus.notify(new Notification("Updater",
505 "Failed to download patch file",
507 NotificationType.ERROR));
512 }, IdeBundle.message("update.downloading.patch.progress.title"), true, null)) {
513 return DownloadPatchResult.CANCELED;
519 private static void doDownloadAndInstallPatch(BuildInfo newVersion, ProgressIndicator i) throws IOException {
520 PatchInfo patch = newVersion.findPatchForCurrentBuild();
521 if (patch == null) throw new IOException("No patch is available for current version");
523 String productCode = ApplicationInfo.getInstance().getBuild().getProductCode();
525 String osSuffix = "";
526 if (SystemInfo.isWindows) {
529 else if (SystemInfo.isMac) {
532 else if (SystemInfo.isUnix) osSuffix = "-unix";
534 String fromBuildNumber = patch.getFromBuild().asStringWithoutProductCode();
535 String toBuildNumber = newVersion.getNumber().asStringWithoutProductCode();
536 String fileName = productCode + "-" + fromBuildNumber + "-" + toBuildNumber + "-patch" + osSuffix + ".jar";
537 URLConnection connection = null;
538 InputStream in = null;
539 OutputStream out = null;
541 String platform = PlatformUtils.getPlatformPrefix();
542 String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase();
543 File patchFile = new File(FileUtil.getTempDirectory(), patchFileName);
546 connection = new URL(new URL(getPatchesUrl()), fileName).openConnection();
547 in = UrlConnectionUtil.getConnectionInputStreamWithException(connection, i);
548 out = new BufferedOutputStream(new FileOutputStream(patchFile));
550 i.setIndeterminate(false);
552 byte[] buffer = new byte[10 * 1024];
553 int total = connection.getContentLength();
557 while ((count = in.read(buffer)) > 0) {
559 out.write(buffer, 0, count);
561 i.setFraction(((double)read) / total);
562 i.setText2((read / 1024) + "/" + (total / 1024) + " KB");
565 catch (IOException e) {
569 catch (ProcessCanceledException e) {
573 catch (Throwable e) {
575 throw new RuntimeException(e);
578 if (out != null) out.close();
579 if (in != null) in.close();
580 if (connection instanceof HttpURLConnection) ((HttpURLConnection)connection).disconnect();
584 public static Set<String> getDisabledToUpdatePlugins() {
585 if (ourDisabledToUpdatePlugins == null) {
586 ourDisabledToUpdatePlugins = new TreeSet<String>();
587 if (!ApplicationManager.getApplication().isUnitTestMode()) {
589 final File file = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
591 final String[] ids = FileUtil.loadFile(file).split("[\\s]");
592 for (String id : ids) {
593 if (id != null && id.trim().length() > 0) {
594 ourDisabledToUpdatePlugins.add(id.trim());
599 catch (IOException e) {
604 return ourDisabledToUpdatePlugins;
607 public static void saveDisabledToUpdatePlugins() {
609 File plugins = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
610 FileUtil.ensureCanCreateFile(plugins);
612 PrintWriter printWriter = null;
614 printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins)));
615 for (String id : getDisabledToUpdatePlugins()) {
616 printWriter.println(id);
621 if (printWriter != null) {
626 catch (IOException e) {