View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
3    * 
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
7    * 
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    * 
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.
15   */
16  
17  package com.atlassian.theplugin.idea.autoupdate;
18  
19  import com.atlassian.theplugin.commons.configuration.GeneralConfigurationBean;
20  import com.atlassian.theplugin.commons.util.LoggerImpl;
21  import com.atlassian.theplugin.idea.IdeaActionScheduler;
22  import com.atlassian.theplugin.util.InfoServer;
23  import com.atlassian.theplugin.util.PluginUtil;
24  import com.intellij.ide.plugins.IdeaPluginDescriptor;
25  import com.intellij.ide.plugins.PluginManager;
26  import com.intellij.ide.startup.StartupActionScriptManager;
27  import com.intellij.openapi.application.ApplicationManager;
28  import com.intellij.openapi.application.PathManager;
29  import com.intellij.openapi.extensions.PluginId;
30  import com.intellij.openapi.ui.DialogWrapper;
31  import com.intellij.openapi.ui.Messages;
32  import com.intellij.openapi.util.io.FileUtil;
33  import com.intellij.openapi.util.io.StreamUtil;
34  import com.intellij.util.io.ZipUtil;
35  import org.jetbrains.annotations.NotNull;
36  
37  import java.io.*;
38  import java.net.HttpURLConnection;
39  import java.net.URL;
40  import java.net.URLConnection;
41  import java.net.URLEncoder;
42  
43  /**
44   * Created by IntelliJ IDEA.
45   * User: Jacek
46   * Date: 2008-02-20
47   * Time: 11:37:09
48   * To change this template use File | Settings | File Templates.
49   */
50  
51  public class PluginDownloader {
52  
53  
54  	public static final String PLUGIN_ID_TOKEN = "PLUGIN_ID";
55  	public static final String VERSION_TOKEN = "BUILD";
56  
57  	private static String pluginName = PluginUtil.getInstance().getName();
58  
59  	private static final int TIMEOUT = 15000;
60  	private static final int EXTENTION_LENGHT = 3;
61  	private InfoServer.VersionInfo newVersion;
62  	private GeneralConfigurationBean updateConfiguration;
63  
64  	public void setTimeout(int timeout) {
65  		this.timeout = timeout;
66  	}
67  
68  	public void setReadTimeout(int readTimeout) {
69  		this.readTimeout = readTimeout;
70  	}
71  
72  	private int timeout = TIMEOUT;
73  	private int readTimeout = TIMEOUT;
74  
75  	public PluginDownloader(InfoServer.VersionInfo newVersion, GeneralConfigurationBean updateConfiguration) {
76  		this.newVersion = newVersion;
77  		this.updateConfiguration = updateConfiguration;
78  	}
79  
80  	public void run() {
81  		try {
82  			final File tmpDownloadFile = downloadPluginFromServer(
83  					this.newVersion.getDownloadUrl(), new File(PathManager.getPluginsPath()));
84  
85  			// add startup actions
86  
87  			// todo lguminski/jjaroczynski to find a better way of getting plugin descriptor
88  			// theoritically openapi should provide a method so the plugin could get info on itself
89  
90  			IdeaPluginDescriptor pluginDescr = PluginManager.getPlugin(PluginId.getId(PluginUtil.getInstance().getPluginId()));
91  			/* todo lguminsk when you debug the plugin it appears in registry as attlassian-idea-plugin, but when
92  			 	you rinstall it notmally it appears as Atlassian. Thats why it is double checked here
93  			    */
94  			if (pluginDescr == null) {
95  				pluginDescr = PluginManager.getPlugin(PluginId.getId(PluginUtil.getInstance().getName()));
96  			}
97  			
98  			if (pluginDescr == null) {
99  				IdeaActionScheduler.getInstance().invokeLater(new Runnable() {
100 					public void run() {
101 						// todo add project or parent to the below window
102 						Messages.showErrorDialog("Cannot retrieve plugin descriptor", "Error installing plugin");
103 					}
104 				});
105 				return;
106 			}
107 			addActions(pluginDescr, tmpDownloadFile);
108 
109 			// restart IDEA
110 			promptShutdownAndShutdown();
111 
112 		} catch (final IOException e) {
113 			PluginUtil.getLogger().warn(e.getMessage(), e);
114 			IdeaActionScheduler.getInstance().invokeLater(new Runnable() {
115 				public void run() {
116 					Messages.showErrorDialog(e.getMessage(), "Error downloading and installing plugin");
117 				}
118 			});
119 
120 		}
121 	}
122 
123 	private void promptShutdownAndShutdown() {
124 		ApplicationManager.getApplication().invokeLater(new Runnable() {
125 			public void run() {
126 				String title = "IDEA shutdown";
127 				String message =
128 						"Atlassian IntelliJ Connector has been installed successfully.\n"
129 						+ "IntelliJ IDEA needs to be restarted to activate the plugin.\n"
130 						+ "Would you like to shutdown IntelliJ IDEA now?";
131 				// todo again add project or parent to the below window
132 				int answer = Messages.showYesNoDialog(
133 						message, title, Messages.getQuestionIcon());
134 				if (answer == DialogWrapper.OK_EXIT_CODE) {
135 					//ApplicationManagerEx.getApplicationEx().exit(true);
136 					ApplicationManager.getApplication().exit();
137 				}
138 			}
139 		});
140 	}
141 
142 	// todo add info about licence and author
143 	File downloadPluginFromServer(String version, @NotNull final File destinationDir) throws IOException {
144 		File pluginArchiveFile = FileUtil.createTempFile("temp_" + pluginName + "_", "tmp");
145 
146 		String pluginUrl = newVersion.getDownloadUrl()
147 				.replaceAll(PLUGIN_ID_TOKEN, URLEncoder.encode(pluginName, "UTF-8"))
148 				.replaceAll(VERSION_TOKEN, URLEncoder.encode(version, "UTF-8"));
149 		if (!pluginUrl.contains("?")) {
150 			pluginUrl += "?";
151 		}
152 		pluginUrl += "uid=" + URLEncoder.encode(Long.toString(updateConfiguration.getUid()), "UTF-8");
153 
154 		PluginUtil.getLogger().info("Downloading plugin archive from: " + pluginUrl);
155 
156 		//HttpConfigurable.getInstance().prepareURL(pluginUrl);
157 		URL url = new URL(pluginUrl);
158 		URLConnection connection = url.openConnection();
159 		connection.setConnectTimeout(getTimeout());
160 		connection.setReadTimeout(getReadTimeout());
161 		connection.connect();
162 
163 		InputStream inputStream = null;
164 		OutputStream outputStream = null;
165 		try {
166 			inputStream = connection.getInputStream();
167 			outputStream = new FileOutputStream(pluginArchiveFile);
168 			StreamUtil.copyStreamContent(inputStream, outputStream);
169 		} catch (FileNotFoundException e) {
170 			PluginUtil.getLogger().warn("File not found " + pluginArchiveFile.getPath(), e);
171 			throw e;
172 		} catch (IOException e) {
173 			PluginUtil.getLogger().warn(e);
174 			throw e;
175 		} finally {
176 
177 			if (inputStream != null) {
178 				try {
179 					inputStream.close();
180 				} catch (IOException ioe) {
181 					PluginUtil.getLogger().warn("Exception while closing input stream");
182 					// nothing we can do at this point
183 				}
184 			}
185 
186 			if (outputStream != null) {
187 				try {
188 					outputStream.close();
189 				} catch (IOException ioe) {
190 					PluginUtil.getLogger().warn("Exception while closing output stream");
191 					// nothing we can do at this point
192 				}
193 			}
194 
195 			if (connection instanceof HttpURLConnection) {
196 				((HttpURLConnection) connection).disconnect();
197 				PluginUtil.getLogger().info("Disconnecting HttpURLConnection");
198 			}
199 		}
200 		PluginUtil.getLogger().info("Downloaded file has [" + pluginArchiveFile.length() + "] bytes");
201 		String srcName = connection.getURL().toString();
202 		String ext = srcName.substring(srcName.lastIndexOf("."));
203 		if (ext.contains("?")) {
204 			ext = ext.substring(0, ext.indexOf("?"));
205 		}
206 		String newName = pluginArchiveFile.getName().substring(0, pluginArchiveFile.getName().length()
207 				- EXTENTION_LENGHT) + ext;
208 		File newFile = new File(destinationDir, newName);
209 		PluginUtil.getLogger().info("Renaming downloaded file from [" + pluginArchiveFile.getAbsolutePath()
210 				+ "] to [" + newFile + "]");
211 
212 		if (!pluginArchiveFile.renameTo(newFile)) {
213 			try {
214 				FileUtil.copy(pluginArchiveFile, newFile);
215 			} catch (IOException e) {
216 				throw new IOException("Renaming file from [" + pluginArchiveFile.getAbsolutePath() + "] to ["
217 						+ newFile.getAbsolutePath() + "] failed.");
218 			} finally {
219 				if (!pluginArchiveFile.delete()) {
220 					LoggerImpl.getInstance().warn("Deleting file [" + pluginArchiveFile.getAbsolutePath() + "] failed.");
221 				}
222 			}
223 		}
224 		
225 		PluginUtil.getLogger().info("After renaming file has [" + newFile.length() + "] bytes");
226 		return newFile;
227 	}
228 
229 	private void addActions(@NotNull final IdeaPluginDescriptor installedPlugin, File localArchiveFile) throws IOException {
230 		PluginUtil.getLogger().info("IdeaPluginDescriptor [" + installedPlugin.getPluginId() + "]");
231 
232 		PluginId id = installedPlugin.getPluginId();
233 
234 		if (PluginManager.isPluginInstalled(id)) {
235 			// store old plugins file
236 			File oldFile = installedPlugin.getPath();
237 			StartupActionScriptManager.ActionCommand deleteOld = new StartupActionScriptManager.DeleteCommand(oldFile);
238 			StartupActionScriptManager.addActionCommand(deleteOld);
239 			PluginUtil.getLogger().info("Queueing deletion of [" + oldFile.getPath() + "], exists [" + oldFile.exists() + "]");
240 		} else {
241 			// we shoud not be here
242 			PluginUtil.getLogger().warn("Install error. Cannot find plugin [" + installedPlugin.getName()
243 					+ "] with id [" + id.getIdString() + "]. Cannot delete old plugin version installing new version");
244 		}
245 
246 		//noinspection HardCodedStringLiteral
247 		boolean isJarFile = localArchiveFile.getName().endsWith(".jar")
248 				|| localArchiveFile.getName().contains(".jar?");
249 
250 		if (isJarFile) {
251 			// add command to copy file to the IDEA/plugins path
252 			String fileName = localArchiveFile.getName();
253 			File newFile = new File(PathManager.getPluginsPath() + File.separator + fileName);
254 			StartupActionScriptManager.ActionCommand copyPlugin =
255 					new StartupActionScriptManager.CopyCommand(localArchiveFile, newFile);
256 			StartupActionScriptManager.addActionCommand(copyPlugin);
257 			PluginUtil.getLogger().info("Queueing copying of jar [" + localArchiveFile.getAbsolutePath() + "] to ["
258 					+ newFile.getAbsolutePath() + "]");
259 		} else {
260 			// add command to unzip file to the IDEA/plugins path
261 			String unzipPath;
262 			if (ZipUtil.isZipContainsFolder(localArchiveFile)) {
263 				unzipPath = PathManager.getPluginsPath();
264 				PluginUtil.getLogger().info("Zip [" + localArchiveFile + "] contains a root folder");
265 			} else {
266 				String dirName = installedPlugin.getName();
267 				unzipPath = PathManager.getPluginsPath() + File.separator + dirName;
268 				PluginUtil.getLogger().info("Zip [" + localArchiveFile + "] does not contain a root folder");
269 			}
270 
271 			File newFile = new File(unzipPath);
272 			StartupActionScriptManager.ActionCommand unzip =
273 					new StartupActionScriptManager.UnzipCommand(localArchiveFile, newFile);
274 			StartupActionScriptManager.addActionCommand(unzip);
275 			PluginUtil.getLogger().info("Queueing unzipping/copying of [" + localArchiveFile.getAbsolutePath() + "] to ["
276 					+ newFile.getAbsolutePath() + "]");
277 		}
278 
279 		// add command to remove temp plugin file
280 		StartupActionScriptManager.ActionCommand deleteTemp =
281 				new StartupActionScriptManager.DeleteCommand(localArchiveFile);
282 		StartupActionScriptManager.addActionCommand(deleteTemp);
283 		PluginUtil.getLogger().info("Queueing deletion of [" + localArchiveFile.getAbsolutePath() + "]");
284 	}
285 
286 	public int getTimeout() {
287 		return timeout;
288 	}
289 
290 	public int getReadTimeout() {
291 		return readTimeout;
292 	}
293 }