2 * Copyright 2000-2014 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.startup.StartupActionScriptManager;
21 import com.intellij.ide.util.PropertiesComponent;
22 import com.intellij.openapi.application.Application;
23 import com.intellij.openapi.application.ApplicationInfo;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.PathManager;
26 import com.intellij.openapi.application.ex.ApplicationInfoEx;
27 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.extensions.PluginId;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.util.BuildNumber;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.util.PathUtil;
36 import com.intellij.util.io.HttpRequests;
37 import com.intellij.util.io.ZipUtil;
38 import org.apache.http.client.utils.URIBuilder;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
43 import java.io.IOException;
45 import java.util.List;
46 import java.util.UUID;
52 public class PluginDownloader {
53 private static final Logger LOG = Logger.getInstance("#" + PluginDownloader.class.getName());
55 private static final String FILENAME = "filename=";
57 private final String myPluginId;
58 private final String myPluginUrl;
59 private String myPluginVersion;
60 private String myFileName;
61 private String myPluginName;
62 private BuildNumber myBuildNumber;
63 private boolean myForceHttps;
66 private File myOldFile;
67 private String myDescription;
68 private List<PluginId> myDepends;
69 private IdeaPluginDescriptor myDescriptor;
71 private PluginDownloader(@NotNull String pluginId,
72 @NotNull String pluginUrl,
73 @Nullable String pluginVersion,
74 @Nullable String fileName,
75 @Nullable String pluginName,
76 @Nullable BuildNumber buildNumber) {
77 myPluginId = pluginId;
78 myPluginUrl = pluginUrl;
79 myPluginVersion = pluginVersion;
80 myFileName = fileName;
81 myPluginName = pluginName;
82 myBuildNumber = buildNumber;
85 public String getPluginId() {
89 public String getPluginVersion() {
90 return myPluginVersion;
93 public String getFileName() {
94 if (myFileName == null) {
95 myFileName = myPluginUrl.substring(myPluginUrl.lastIndexOf('/') + 1);
100 public String getPluginName() {
101 if (myPluginName == null) {
102 myPluginName = FileUtil.getNameWithoutExtension(getFileName());
107 public BuildNumber getBuildNumber() {
108 return myBuildNumber;
111 public String getDescription() {
112 return myDescription;
115 public void setDescription(String description) {
116 myDescription = description;
119 public List<PluginId> getDepends() {
123 public void setDepends(List<PluginId> depends) {
127 public IdeaPluginDescriptor getDescriptor() {
131 public void setDescriptor(IdeaPluginDescriptor descriptor) {
132 myDescriptor = descriptor;
135 public void setForceHttps(boolean forceHttps) {
136 myForceHttps = forceHttps;
139 public boolean prepareToInstall(@NotNull ProgressIndicator indicator) throws IOException {
140 if (myFile != null) {
144 IdeaPluginDescriptor descriptor = null;
145 if (!Boolean.getBoolean(StartupActionScriptManager.STARTUP_WIZARD_MODE) && PluginManager.isPluginInstalled(PluginId.getId(myPluginId))) {
146 //store old plugins file
147 descriptor = PluginManager.getPlugin(PluginId.getId(myPluginId));
148 LOG.assertTrue(descriptor != null);
149 if (myPluginVersion != null && compareVersionsSkipBroken(descriptor, myPluginVersion) <= 0) {
150 LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion);
153 myOldFile = descriptor.getPath();
157 String errorMessage = IdeBundle.message("unknown.error");
159 myFile = downloadPlugin(indicator);
161 catch (IOException ex) {
164 errorMessage = ex.getMessage();
166 if (myFile == null) {
167 if (ApplicationManager.getApplication() != null) {
168 final String text = IdeBundle.message("error.plugin.was.not.installed", getPluginName(), errorMessage);
169 final String title = IdeBundle.message("title.failed.to.download");
170 ApplicationManager.getApplication().invokeLater(new Runnable() {
173 Messages.showErrorDialog(text, title);
180 IdeaPluginDescriptorImpl actualDescriptor = loadDescriptionFromJar(myFile);
181 if (actualDescriptor != null) {
182 if (InstalledPluginsState.getInstance().wasUpdated(actualDescriptor.getPluginId())) {
183 return false; //already updated
186 myPluginVersion = actualDescriptor.getVersion();
187 if (descriptor != null && compareVersionsSkipBroken(descriptor, myPluginVersion) <= 0) {
188 LOG.info("Plugin " + myPluginId + ": current version (max) " + myPluginVersion);
189 return false; //was not updated
192 setDescriptor(actualDescriptor);
194 if (PluginManagerCore.isIncompatible(actualDescriptor, myBuildNumber)) {
195 LOG.info("Plugin " + myPluginId + " is incompatible with current installation " +
196 "(since:" + actualDescriptor.getSinceBuild() + " until:" + actualDescriptor.getUntilBuild() + ")");
197 return false; //host outdated plugins, no compatible plugin for new version
203 public static int compareVersionsSkipBroken(IdeaPluginDescriptor descriptor, String newPluginVersion) {
204 int state = StringUtil.compareVersionNumbers(newPluginVersion, descriptor.getVersion());
205 if (PluginManagerCore.isBrokenPlugin(descriptor) && state < 0) {
212 public static IdeaPluginDescriptorImpl loadDescriptionFromJar(final File file) throws IOException {
213 IdeaPluginDescriptorImpl descriptor = PluginManagerCore.loadDescriptorFromJar(file);
214 if (descriptor == null) {
215 if (file.getName().endsWith(".zip")) {
216 final File outputDir = FileUtil.createTempDirectory("plugin", "");
218 ZipUtil.extract(file, outputDir, null);
219 final File[] files = outputDir.listFiles();
220 if (files != null && files.length == 1) {
221 descriptor = PluginManagerCore.loadDescriptor(files[0], PluginManagerCore.PLUGIN_XML);
225 FileUtil.delete(outputDir);
232 public void install() throws IOException {
233 LOG.assertTrue(myFile != null);
234 if (myOldFile != null) {
235 // add command to delete the 'action script' file
236 StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(myOldFile);
237 StartupActionScriptManager.addActionCommand(deleteOld);
239 PluginInstaller.install(myFile, getPluginName(), true);
240 InstalledPluginsState.getInstance().onPluginInstall(myDescriptor);
244 private File downloadPlugin(@NotNull final ProgressIndicator indicator) throws IOException {
245 File pluginsTemp = new File(PathManager.getPluginTempPath());
246 if (!pluginsTemp.exists() && !pluginsTemp.mkdirs()) {
247 throw new IOException(IdeBundle.message("error.cannot.create.temp.dir", pluginsTemp));
249 final File file = FileUtil.createTempFile(pluginsTemp, "plugin_", "_download", true, false);
251 indicator.checkCanceled();
252 indicator.setText2(IdeBundle.message("progress.downloading.plugin", getPluginName()));
254 return HttpRequests.request(myPluginUrl).gzip(false).forceHttps(myForceHttps).connect(new HttpRequests.RequestProcessor<File>() {
256 public File process(@NotNull HttpRequests.Request request) throws IOException {
257 request.saveToFile(file, indicator);
258 if (myFileName == null) {
259 myFileName = guessFileName(request.getConnection(), file);
262 File newFile = new File(file.getParentFile(), myFileName);
263 FileUtil.rename(file, newFile);
270 private String guessFileName(@NotNull URLConnection connection, @NotNull File file) throws IOException {
271 String fileName = null;
273 final String contentDisposition = connection.getHeaderField("Content-Disposition");
274 LOG.debug("header: " + contentDisposition);
276 if (contentDisposition != null && contentDisposition.contains(FILENAME)) {
277 final int startIdx = contentDisposition.indexOf(FILENAME);
278 final int endIdx = contentDisposition.indexOf(';', startIdx);
279 fileName = contentDisposition.substring(startIdx + FILENAME.length(), endIdx > 0 ? endIdx : contentDisposition.length());
281 if (StringUtil.startsWithChar(fileName, '\"') && StringUtil.endsWithChar(fileName, '\"')) {
282 fileName = fileName.substring(1, fileName.length() - 1);
286 if (fileName == null) {
287 // try to find a filename in an URL
288 final String usedURL = connection.getURL().toString();
289 fileName = usedURL.substring(usedURL.lastIndexOf('/') + 1);
290 if (fileName.length() == 0 || fileName.contains("?")) {
291 fileName = myPluginUrl.substring(myPluginUrl.lastIndexOf('/') + 1);
295 if (!PathUtil.isValidFileName(fileName)) {
296 FileUtil.delete(file);
297 throw new IOException("Invalid filename returned by a server");
303 // creators-converters
305 public static PluginDownloader createDownloader(@NotNull IdeaPluginDescriptor descriptor) throws IOException {
306 return createDownloader(descriptor, null, null);
310 public static PluginDownloader createDownloader(@NotNull IdeaPluginDescriptor descriptor,
311 @Nullable String host,
312 @Nullable BuildNumber buildNumber) throws IOException {
314 PluginId id = descriptor.getPluginId();
315 String url = host != null ? getHostUrl(host, descriptor.getUrl()) : getRepositoryUrl(id, buildNumber);
316 PluginDownloader downloader = new PluginDownloader(id.getIdString(), url, descriptor.getVersion(), null, descriptor.getName(), null);
317 downloader.setDescriptor(descriptor);
318 downloader.setDescription(descriptor.getDescription());
319 downloader.setDepends(((PluginNode)descriptor).getDepends());
322 catch (URISyntaxException e) {
323 throw new IOException(e);
328 private static String getHostUrl(@NotNull String host, @NotNull String pluginUrl) throws URISyntaxException, MalformedURLException {
329 if (new URI(pluginUrl).isAbsolute()) {
333 return new URL(new URL(host), pluginUrl).toExternalForm();
338 private static String getRepositoryUrl(@NotNull PluginId pluginId, @Nullable BuildNumber buildNumber) throws URISyntaxException {
339 Application app = ApplicationManager.getApplication();
340 ApplicationInfoEx appInfo = ApplicationInfoImpl.getShadowInstance();
342 String buildNumberAsString = buildNumber != null ? buildNumber.asString() :
343 app != null ? ApplicationInfo.getInstance().getApiVersion() :
344 appInfo.getBuild().asString();
346 String uuid = app != null ? UpdateChecker.getInstallationUID(PropertiesComponent.getInstance()) : UUID.randomUUID().toString();
348 URIBuilder uriBuilder = new URIBuilder(appInfo.getPluginsDownloadUrl());
349 uriBuilder.addParameter("action", "download");
350 uriBuilder.addParameter("id", pluginId.getIdString());
351 uriBuilder.addParameter("build", buildNumberAsString);
352 uriBuilder.addParameter("uuid", uuid);
353 return uriBuilder.toString();
357 public static PluginNode createPluginNode(@Nullable String host, @NotNull PluginDownloader downloader) {
358 IdeaPluginDescriptor descriptor = downloader.getDescriptor();
359 if (descriptor instanceof PluginNode) {
360 return (PluginNode)descriptor;
363 PluginNode node = new PluginNode(PluginId.getId(downloader.getPluginId()));
364 node.setName(downloader.getPluginName());
365 node.setVersion(downloader.getPluginVersion());
366 node.setRepositoryName(host);
367 node.setDownloadUrl(downloader.myPluginUrl);
368 node.setDepends(downloader.getDepends(), null);
369 node.setDescription(downloader.getDescription());