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.
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
;
61 import java
.net
.HttpURLConnection
;
63 import java
.net
.URLConnection
;
65 import java
.util
.concurrent
.Future
;
66 import java
.util
.concurrent
.TimeUnit
;
67 import java
.util
.concurrent
.TimeoutException
;
73 * <version>4.5.2</version>
74 * <title>New Intellij IDEA Version</title>
76 * New version of IntelliJ IDEA is available.
77 * 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
= "";
86 public static enum DownloadPatchResult
{
87 SUCCESS
, FAILED
, CANCELED
90 private static long checkInterval
= 0;
91 private static boolean myVeryFirstOpening
= true;
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
)) {
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()) {
149 checkPluginsHost(host
, downloaded
);
151 catch (Exception e
) {
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"));
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");
183 if (pluginUrl
== null) {
184 LOG
.info("plugin url should not be null");
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() {
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
) {
217 }, IdeBundle
.message("update.uploading.plugin.progress.title"), true, null);
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
);
230 if (name
.equals(".")) {
233 else if (name
.equals("..")) {
234 child
= hostFile
.getParent();
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
);
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
)) {
262 public static UpdateChannel
checkForUpdates() throws ConnectionException
{
264 BuildNumber ourBuild
= ApplicationInfo
.getInstance().getBuild();
265 if (LOG
.isDebugEnabled()) {
266 LOG
.debug("enter: checkForUpdates()");
269 final Document document
;
271 document
= loadVersionInfo(getUpdateUrl());
272 if (document
== null) return null;
274 catch (Throwable 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) {
292 catch (Throwable t
) {
297 UpdateSettings
.getInstance().LAST_TIME_CHECKED
= System
.currentTimeMillis();
302 private static UpdateChannel
findUpdateChannel(Element root
, String productCode
) {
304 LOG
.info("cannot read " + getUpdateUrl());
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) {
322 BuildInfo build
= channel
.getLatestBuild();
323 assert build
!= null;
325 if (build
.compareTo(cBuild
) < 0) {
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() {
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();
347 document
[0] = JDOMUtil
.loadDocument(inputStream
);
353 catch (IOException e
) {
356 catch (JDOMException e
) {
357 LOG
.info(e
); // Broken xml downloaded. Don't bother telling user.
363 downloadThreadFuture
.get(5, TimeUnit
.SECONDS
);
365 catch (TimeoutException e
) {
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];
378 public static void showNoUpdatesDialog(boolean enableLink
, final List
<PluginDownloader
> updatePlugins
) {
379 NoUpdatesDialog dialog
= new NoUpdatesDialog(true, updatePlugins
, enableLink
);
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;
392 downloader
.install();
395 catch (IOException e
) {
403 public static DownloadPatchResult
downloadAndInstallPatch(final BuildInfo newVersion
) {
404 final DownloadPatchResult
[] result
= new DownloadPatchResult
[]{DownloadPatchResult
.CANCELED
};
406 if (!ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
409 doDownloadAndInstallPatch(newVersion
, ProgressManager
.getInstance().getProgressIndicator());
410 result
[0] = DownloadPatchResult
.SUCCESS
;
412 catch (final IOException e
) {
414 result
[0] = DownloadPatchResult
.FAILED
;
416 SwingUtilities
.invokeLater(new Runnable() {
418 Notifications
.Bus
.notify(new Notification("Updater",
419 "Failed to download patch file",
421 NotificationType
.ERROR
));
426 }, IdeBundle
.message("update.downloading.patch.progress.title"), true, null)) {
427 return DownloadPatchResult
.CANCELED
;
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
) {
443 else if (SystemInfo
.isMac
) {
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
);
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();
471 while ((count
= in
.read(buffer
)) > 0) {
473 out
.write(buffer
, 0, count
);
475 i
.setFraction(((double)read
) / total
);
476 i
.setText2((read
/ 1024) + "/" + (total
/ 1024) + " KB");
479 catch (IOException e
) {
483 catch (ProcessCanceledException e
) {
487 catch (Throwable e
) {
489 throw new RuntimeException(e
);
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()) {
503 final File file
= new File(PathManager
.getConfigPath(), DISABLED_UPDATE
);
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
) {
518 return ourDisabledToUpdatePlugins
;
521 public static void saveDisabledToUpdatePlugins() {
523 File plugins
= new File(PathManager
.getConfigPath(), DISABLED_UPDATE
);
524 FileUtil
.ensureCanCreateFile(plugins
);
526 PrintWriter printWriter
= null;
528 printWriter
= new PrintWriter(new BufferedWriter(new FileWriter(plugins
)));
529 for (String id
: getDisabledToUpdatePlugins()) {
530 printWriter
.println(id
);
535 if (printWriter
!= null) {
540 catch (IOException e
) {