Fix updater by removing non-working channel switching ability and always resorting...
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / updateSettings / impl / UpdateChecker.java
blobe3f39d7b317b142b4914f35b8329f4ee72ea1b2a
1 /*
2 * Copyright 2000-2009 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.
18 * Created by IntelliJ IDEA.
19 * User: mike
20 * Date: Oct 31, 2002
21 * Time: 6:33:01 PM
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com.intellij.openapi.updateSettings.impl;
27 import com.intellij.ide.IdeBundle;
28 import com.intellij.ide.reporter.ConnectionException;
29 import com.intellij.notification.Notification;
30 import com.intellij.notification.NotificationType;
31 import com.intellij.notification.Notifications;
32 import com.intellij.openapi.application.ApplicationInfo;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.application.PathManager;
35 import com.intellij.openapi.application.ex.ApplicationInfoEx;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.progress.ProcessCanceledException;
38 import com.intellij.openapi.progress.ProgressIndicator;
39 import com.intellij.openapi.progress.ProgressManager;
40 import com.intellij.openapi.ui.Messages;
41 import com.intellij.openapi.util.BuildNumber;
42 import com.intellij.openapi.util.JDOMUtil;
43 import com.intellij.openapi.util.SystemInfo;
44 import com.intellij.openapi.util.io.FileUtil;
45 import com.intellij.openapi.util.text.StringUtil;
46 import com.intellij.openapi.vfs.VfsUtil;
47 import com.intellij.openapi.vfs.VirtualFile;
48 import com.intellij.openapi.vfs.ex.http.HttpFileSystem;
49 import com.intellij.util.io.UrlConnectionUtil;
50 import com.intellij.util.net.HttpConfigurable;
51 import com.intellij.util.text.DateFormatUtil;
52 import org.jdom.Document;
53 import org.jdom.Element;
54 import org.jdom.JDOMException;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.NotNull;
57 import org.jetbrains.annotations.Nullable;
59 import javax.swing.*;
60 import java.io.*;
61 import java.net.HttpURLConnection;
62 import java.net.URL;
63 import java.net.URLConnection;
64 import java.util.*;
65 import java.util.concurrent.Future;
66 import java.util.concurrent.TimeUnit;
67 import java.util.concurrent.TimeoutException;
69 /**
70 * XML sample:
71 * <idea>
72 * <build>456</build>
73 * <version>4.5.2</version>
74 * <title>New Intellij IDEA Version</title>
75 * <message>
76 * New version of IntelliJ IDEA is available.
77 * Please visit http://www.intellij.com/ for more info.
78 * </message>
79 * </idea>
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 = "";
86 public static enum DownloadPatchResult {
87 SUCCESS, FAILED, CANCELED
90 private static long checkInterval = 0;
91 private static boolean myVeryFirstOpening = true;
94 @NonNls
95 private static final String DISABLED_UPDATE = "disabled_update.txt";
96 private static TreeSet<String> ourDisabledToUpdatePlugins;
98 private static class StringHolder {
99 private static final String UPDATE_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getCheckingUrl();
100 private static final String PATCHES_URL = ApplicationInfoEx.getInstanceEx().getUpdateUrls().getPatchesUrl();
103 private static String getUpdateUrl() {
104 return StringHolder.UPDATE_URL;
107 private static String getPatchesUrl() {
108 return StringHolder.PATCHES_URL;
111 public static boolean isMyVeryFirstOpening() {
112 return myVeryFirstOpening;
115 public static void setMyVeryFirstOpening(final boolean myVeryFirstProjectOpening) {
116 myVeryFirstOpening = myVeryFirstProjectOpening;
119 public static boolean checkNeeded() {
121 final UpdateSettings settings = UpdateSettings.getInstance();
122 if (settings == null || getUpdateUrl() == null) return false;
124 final String checkPeriod = settings.CHECK_PERIOD;
125 if (checkPeriod.equals(UpdateSettingsConfigurable.ON_START_UP)) {
126 checkInterval = 0;
128 if (checkPeriod.equals(UpdateSettingsConfigurable.DAILY)) {
129 checkInterval = DateFormatUtil.DAY;
131 if (settings.CHECK_PERIOD.equals(UpdateSettingsConfigurable.WEEKLY)) {
132 checkInterval = DateFormatUtil.WEEK;
134 if (settings.CHECK_PERIOD.equals(UpdateSettingsConfigurable.MONTHLY)) {
135 checkInterval = DateFormatUtil.MONTH;
138 final long timeDelta = System.currentTimeMillis() - settings.LAST_TIME_CHECKED;
139 if (Math.abs(timeDelta) < checkInterval) return false;
141 return settings.CHECK_NEEDED;
144 public static List<PluginDownloader> updatePlugins(final boolean showErrorDialog) {
145 final List<PluginDownloader> downloaded = new ArrayList<PluginDownloader>();
146 final Set<String> failed = new HashSet<String>();
147 for (String host : UpdateSettingsConfigurable.getInstance().getPluginHosts()) {
148 try {
149 checkPluginsHost(host, downloaded);
151 catch (Exception e) {
152 LOG.info(e);
153 failed.add(host);
156 if (!failed.isEmpty()) {
157 final String failedMessage = IdeBundle.message("connection.failed.message", StringUtil.join(failed, ","));
158 if (showErrorDialog) {
159 Messages.showErrorDialog(failedMessage, IdeBundle.message("title.connection.error"));
161 else {
162 LOG.info(failedMessage);
165 return downloaded.isEmpty() ? null : downloaded;
168 public static boolean checkPluginsHost(final String host, final List<PluginDownloader> downloaded) throws Exception {
169 final Document document = loadVersionInfo(host);
170 if (document == null) return false;
171 boolean success = true;
172 for (Object plugin : document.getRootElement().getChildren("plugin")) {
173 Element pluginElement = (Element)plugin;
174 final String pluginId = pluginElement.getAttributeValue("id");
175 String pluginUrl = pluginElement.getAttributeValue("url");
176 final String pluginVersion = pluginElement.getAttributeValue("version");
177 if (pluginId == null) {
178 LOG.info("plugin id should not be null");
179 success = false;
180 continue;
183 if (pluginUrl == null) {
184 LOG.info("plugin url should not be null");
185 success = false;
186 continue;
190 if (!pluginUrl.startsWith(HttpFileSystem.PROTOCOL)) {
191 final HttpFileSystem fileSystem = HttpFileSystem.getInstance();
192 final VirtualFile hostFile = fileSystem.findFileByPath(VfsUtil.urlToPath(host));
193 LOG.assertTrue(hostFile != null);
194 final VirtualFile pluginByRelativePath = findPluginByRelativePath(hostFile.getParent(), pluginUrl, fileSystem);
195 if (pluginByRelativePath != null) {
196 pluginUrl = pluginByRelativePath.getUrl();
200 final String finalPluginUrl = pluginUrl;
201 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
202 public void run() {
203 try {
204 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
205 if (progressIndicator != null) {
206 progressIndicator.setText(finalPluginUrl);
208 final PluginDownloader uploader = new PluginDownloader(pluginId, finalPluginUrl, pluginVersion);
209 if (uploader.prepareToInstall()) {
210 downloaded.add(uploader);
213 catch (IOException e) {
214 LOG.info(e);
217 }, IdeBundle.message("update.uploading.plugin.progress.title"), true, null);
219 return success;
222 @Nullable
223 public static VirtualFile findPluginByRelativePath(VirtualFile hostFile, @NotNull @NonNls String relPath, final HttpFileSystem fileSystem) {
224 if (relPath.length() == 0) return hostFile;
225 int index = relPath.indexOf('/');
226 if (index < 0) index = relPath.length();
227 String name = relPath.substring(0, index);
229 VirtualFile child;
230 if (name.equals(".")) {
231 child = hostFile;
233 else if (name.equals("..")) {
234 child = hostFile.getParent();
236 else {
237 child = fileSystem.findFileByPath(hostFile.getPath() + "/" + name);
240 if (child == null) return null;
242 if (index < relPath.length()) {
243 return findPluginByRelativePath(child, relPath.substring(index + 1), fileSystem);
245 else {
246 return child;
250 @Nullable
251 private static Product findProduct(Element products, String code) {
252 for (Object productNode : products.getChildren("product")) {
253 Product product = new Product((Element)productNode);
254 if (product.hasCode(code)) {
255 return product;
258 return null;
261 @Nullable
262 public static UpdateChannel checkForUpdates() throws ConnectionException {
263 try {
264 BuildNumber ourBuild = ApplicationInfo.getInstance().getBuild();
265 if (LOG.isDebugEnabled()) {
266 LOG.debug("enter: checkForUpdates()");
269 final Document document;
270 try {
271 document = loadVersionInfo(getUpdateUrl());
272 if (document == null) return null;
274 catch (Throwable t) {
275 LOG.debug(t);
276 throw new ConnectionException(t);
279 Element root = document.getRootElement();
280 UpdateChannel channel = findUpdateChannel(root, ourBuild.getProductCode());
281 if (channel == null) return null;
283 BuildInfo latestBuild = channel.getLatestBuild();
284 if (latestBuild == null) return null;
286 if (ourBuild.compareTo(latestBuild.getNumber()) < 0) {
287 return channel;
290 return null;
292 catch (Throwable t) {
293 LOG.debug(t);
294 return null;
296 finally {
297 UpdateSettings.getInstance().LAST_TIME_CHECKED = System.currentTimeMillis();
301 @Nullable
302 private static UpdateChannel findUpdateChannel(Element root, String productCode) {
303 if (root == null) {
304 LOG.info("cannot read " + getUpdateUrl());
305 return null;
308 Product product = findProduct(root, productCode);
309 if (product == null) return null;
311 UpdateChannel channel = product.findUpdateChannelById(ApplicationInfo.getInstance().getDefaultUpdateChannel());
312 if (channel != null) return channel;
314 for (UpdateChannel c : product.getChannels()) {
315 BuildInfo cBuild = c.getLatestBuild();
316 if (cBuild == null) continue;
318 if (channel == null) {
319 channel = c;
321 else {
322 BuildInfo build = channel.getLatestBuild();
323 assert build != null;
325 if (build.compareTo(cBuild) < 0) {
326 channel = c;
331 return channel;
334 private static Document loadVersionInfo(final String url) throws Exception {
335 if (LOG.isDebugEnabled()) {
336 LOG.debug("enter: loadVersionInfo(UPDATE_URL='" + url + "' )");
338 final Document[] document = new Document[]{null};
339 final Exception[] exception = new Exception[]{null};
340 Future<?> downloadThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
341 public void run() {
342 try {
343 HttpConfigurable.getInstance().prepareURL(url);
344 final URL requestUrl = new URL(url + "?build=" + ApplicationInfo.getInstance().getBuild().asString() + ADDITIONAL_REQUEST_OPTIONS);
345 final InputStream inputStream = requestUrl.openStream();
346 try {
347 document[0] = JDOMUtil.loadDocument(inputStream);
349 finally {
350 inputStream.close();
353 catch (IOException e) {
354 exception[0] = e;
356 catch (JDOMException e) {
357 LOG.info(e); // Broken xml downloaded. Don't bother telling user.
362 try {
363 downloadThreadFuture.get(5, TimeUnit.SECONDS);
365 catch (TimeoutException e) {
366 // ignore
369 if (!downloadThreadFuture.isDone()) {
370 downloadThreadFuture.cancel(true);
371 throw new ConnectionException(IdeBundle.message("updates.timeout.error"));
374 if (exception[0] != null) throw exception[0];
375 return document[0];
378 public static void showNoUpdatesDialog(boolean enableLink, final List<PluginDownloader> updatePlugins) {
379 NoUpdatesDialog dialog = new NoUpdatesDialog(true, updatePlugins, enableLink);
380 dialog.show();
383 public static void showUpdateInfoDialog(boolean enableLink, final UpdateChannel channel, final List<PluginDownloader> updatePlugins) {
384 new UpdateInfoDialog(true, channel, updatePlugins, enableLink).show();
387 public static boolean install(List<PluginDownloader> downloaders) {
388 boolean installed = false;
389 for (PluginDownloader downloader : downloaders) {
390 if (getDisabledToUpdatePlugins().contains(downloader.getPluginId())) continue;
391 try {
392 downloader.install();
393 installed = true;
395 catch (IOException e) {
396 LOG.info(e);
399 return installed;
403 public static DownloadPatchResult downloadAndInstallPatch(final BuildInfo newVersion) {
404 final DownloadPatchResult[] result = new DownloadPatchResult[]{DownloadPatchResult.CANCELED};
406 if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
407 public void run() {
408 try {
409 doDownloadAndInstallPatch(newVersion, ProgressManager.getInstance().getProgressIndicator());
410 result[0] = DownloadPatchResult.SUCCESS;
412 catch (final IOException e) {
413 LOG.info(e);
414 result[0] = DownloadPatchResult.FAILED;
416 SwingUtilities.invokeLater(new Runnable() {
417 public void run() {
418 Notifications.Bus.notify(new Notification("Updater",
419 "Failed to download patch file",
420 e.getMessage(),
421 NotificationType.ERROR));
426 }, IdeBundle.message("update.downloading.patch.progress.title"), true, null)) {
427 return DownloadPatchResult.CANCELED;
430 return result[0];
433 private static void doDownloadAndInstallPatch(BuildInfo newVersion, ProgressIndicator i) throws IOException {
434 PatchInfo patch = newVersion.findPatchForCurrentBuild();
435 if (patch == null) throw new IOException("No patch is available for current version");
437 String productCode = ApplicationInfo.getInstance().getBuild().getProductCode();
439 String osSuffix = "";
440 if (SystemInfo.isWindows) {
441 osSuffix = "-win";
443 else if (SystemInfo.isMac) {
444 osSuffix = "-mac";
446 else if (SystemInfo.isUnix) osSuffix = "-unix";
448 String fromBuildNumber = patch.getFromBuild().asStringWithoutProductCode();
449 String toBuildNumber = newVersion.getNumber().asStringWithoutProductCode();
450 String fileName = productCode + "-" + fromBuildNumber + "-" + toBuildNumber + "-patch" + osSuffix + ".jar";
451 URLConnection connection = null;
452 InputStream in = null;
453 OutputStream out = null;
455 String platform = System.getProperty("idea.platform.prefix", "idea");
456 String patchFileName = "jetbrains.patch.jar." + platform;
457 File patchFile = new File(FileUtil.getTempDirectory(), patchFileName);
459 try {
460 connection = new URL(new URL(getPatchesUrl()), fileName).openConnection();
461 in = UrlConnectionUtil.getConnectionInputStreamWithException(connection, i);
462 out = new BufferedOutputStream(new FileOutputStream(patchFile));
464 i.setIndeterminate(false);
466 byte[] buffer = new byte[10 * 1024];
467 int total = connection.getContentLength();
468 int count;
469 int read = 0;
471 while ((count = in.read(buffer)) > 0) {
472 i.checkCanceled();
473 out.write(buffer, 0, count);
474 read += count;
475 i.setFraction(((double)read) / total);
476 i.setText2((read / 1024) + "/" + (total / 1024) + " KB");
479 catch (IOException e) {
480 patchFile.delete();
481 throw e;
483 catch (ProcessCanceledException e) {
484 patchFile.delete();
485 throw e;
487 catch (Throwable e) {
488 patchFile.delete();
489 throw new RuntimeException(e);
491 finally {
492 if (out != null) out.close();
493 if (in != null) in.close();
494 if (connection instanceof HttpURLConnection) ((HttpURLConnection)connection).disconnect();
498 public static Set<String> getDisabledToUpdatePlugins() {
499 if (ourDisabledToUpdatePlugins == null) {
500 ourDisabledToUpdatePlugins = new TreeSet<String>();
501 if (!ApplicationManager.getApplication().isUnitTestMode()) {
502 try {
503 final File file = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
504 if (file.isFile()) {
505 final String[] ids = new String(FileUtil.loadFileText(file)).split("[\\s]");
506 for (String id : ids) {
507 if (id != null && id.trim().length() > 0) {
508 ourDisabledToUpdatePlugins.add(id.trim());
513 catch (IOException e) {
514 LOG.error(e);
518 return ourDisabledToUpdatePlugins;
521 public static void saveDisabledToUpdatePlugins() {
522 try {
523 File plugins = new File(PathManager.getConfigPath(), DISABLED_UPDATE);
524 FileUtil.ensureCanCreateFile(plugins);
526 PrintWriter printWriter = null;
527 try {
528 printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins)));
529 for (String id : getDisabledToUpdatePlugins()) {
530 printWriter.println(id);
532 printWriter.flush();
534 finally {
535 if (printWriter != null) {
536 printWriter.close();
540 catch (IOException e) {
541 LOG.error(e);