source: josm/trunk/src/org/openstreetmap/josm/plugins/ReadRemotePluginInformationTask.java@ 6388

Last change on this file since 6388 was 6257, checked in by Don-vip, 11 years ago

fix #8862, see #9101 - UI message when plugin list/icons download fail

  • Property svn:eol-style set to native
File size: 16.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Dimension;
7import java.awt.GridBagLayout;
8import java.io.BufferedReader;
9import java.io.ByteArrayInputStream;
10import java.io.File;
11import java.io.FileOutputStream;
12import java.io.FilenameFilter;
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.InputStreamReader;
16import java.io.OutputStream;
17import java.io.OutputStreamWriter;
18import java.io.PrintWriter;
19import java.io.UnsupportedEncodingException;
20import java.net.HttpURLConnection;
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.HashSet;
28import java.util.LinkedList;
29import java.util.List;
30
31import javax.swing.JLabel;
32import javax.swing.JOptionPane;
33import javax.swing.JPanel;
34import javax.swing.JScrollPane;
35
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
38import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
39import org.openstreetmap.josm.gui.progress.ProgressMonitor;
40import org.openstreetmap.josm.gui.util.GuiHelper;
41import org.openstreetmap.josm.gui.widgets.JosmTextArea;
42import org.openstreetmap.josm.io.OsmTransferException;
43import org.openstreetmap.josm.tools.GBC;
44import org.openstreetmap.josm.tools.ImageProvider;
45import org.openstreetmap.josm.tools.Utils;
46import org.xml.sax.SAXException;
47
48/**
49 * An asynchronous task for downloading plugin lists from the configured plugin download sites.
50 * @since 2817
51 */
52public class ReadRemotePluginInformationTask extends PleaseWaitRunnable {
53
54 private Collection<String> sites;
55 private boolean canceled;
56 private HttpURLConnection connection;
57 private List<PluginInformation> availablePlugins;
58
59 protected enum CacheType {PLUGIN_LIST, ICON_LIST}
60
61 protected void init(Collection<String> sites){
62 this.sites = sites;
63 if (sites == null) {
64 this.sites = Collections.emptySet();
65 }
66 availablePlugins = new LinkedList<PluginInformation>();
67
68 }
69 /**
70 * Creates the task
71 *
72 * @param sites the collection of download sites. Defaults to the empty collection if null.
73 */
74 public ReadRemotePluginInformationTask(Collection<String> sites) {
75 super(tr("Download plugin list..."), false /* don't ignore exceptions */);
76 init(sites);
77 }
78
79 /**
80 * Creates the task
81 *
82 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null
83 * @param sites the collection of download sites. Defaults to the empty collection if null.
84 */
85 public ReadRemotePluginInformationTask(ProgressMonitor monitor, Collection<String> sites) {
86 super(tr("Download plugin list..."), monitor == null ? NullProgressMonitor.INSTANCE: monitor, false /* don't ignore exceptions */);
87 init(sites);
88 }
89
90
91 @Override
92 protected void cancel() {
93 canceled = true;
94 synchronized(this) {
95 if (connection != null) {
96 connection.disconnect();
97 }
98 }
99 }
100
101 @Override
102 protected void finish() {}
103
104 /**
105 * Creates the file name for the cached plugin list and the icon cache
106 * file.
107 *
108 * @param site the name of the site
109 * @param type icon cache or plugin list cache
110 * @return the file name for the cache file
111 */
112 protected File createSiteCacheFile(File pluginDir, String site, CacheType type) {
113 String name;
114 try {
115 site = site.replaceAll("%<(.*)>", "");
116 URL url = new URL(site);
117 StringBuilder sb = new StringBuilder();
118 sb.append("site-");
119 sb.append(url.getHost()).append("-");
120 if (url.getPort() != -1) {
121 sb.append(url.getPort()).append("-");
122 }
123 String path = url.getPath();
124 for (int i =0;i<path.length(); i++) {
125 char c = path.charAt(i);
126 if (Character.isLetterOrDigit(c)) {
127 sb.append(c);
128 } else {
129 sb.append("_");
130 }
131 }
132 switch (type) {
133 case PLUGIN_LIST:
134 sb.append(".txt");
135 break;
136 case ICON_LIST:
137 sb.append("-icons.zip");
138 break;
139 }
140 name = sb.toString();
141 } catch(MalformedURLException e) {
142 name = "site-unknown.txt";
143 }
144 return new File(pluginDir, name);
145 }
146
147 /**
148 * Downloads the list from a remote location
149 *
150 * @param site the site URL
151 * @param monitor a progress monitor
152 * @return the downloaded list
153 */
154 protected String downloadPluginList(String site, final ProgressMonitor monitor) {
155 BufferedReader in = null;
156 String line;
157 try {
158 /* replace %<x> with empty string or x=plugins (separated with comma) */
159 String pl = Utils.join(",", Main.pref.getCollection("plugins"));
160 String printsite = site.replaceAll("%<(.*)>", "");
161 if(pl != null && pl.length() != 0) {
162 site = site.replaceAll("%<(.*)>", "$1"+pl);
163 } else {
164 site = printsite;
165 }
166
167 monitor.beginTask("");
168 monitor.indeterminateSubTask(tr("Downloading plugin list from ''{0}''", printsite));
169
170 URL url = new URL(site);
171 synchronized(this) {
172 connection = Utils.openHttpConnection(url);
173 connection.setRequestProperty("Cache-Control", "no-cache");
174 connection.setRequestProperty("Accept-Charset", "utf-8");
175 }
176 in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
177 StringBuilder sb = new StringBuilder();
178 while ((line = in.readLine()) != null) {
179 sb.append(line).append("\n");
180 }
181 return sb.toString();
182 } catch (MalformedURLException e) {
183 if (canceled) return null;
184 e.printStackTrace();
185 return null;
186 } catch (IOException e) {
187 if (canceled) return null;
188 handleIOException(monitor, e, tr("Plugin list download error"), tr("JOSM failed to download plugin list:"));
189 return null;
190 } finally {
191 synchronized(this) {
192 if (connection != null) {
193 connection.disconnect();
194 }
195 connection = null;
196 }
197 Utils.close(in);
198 monitor.finishTask();
199 }
200 }
201
202 private void handleIOException(final ProgressMonitor monitor, IOException e, final String title, String firstMessage) {
203 InputStream errStream = connection.getErrorStream();
204 StringBuilder sb = new StringBuilder();
205 if (errStream != null) {
206 BufferedReader err = null;
207 try {
208 String line;
209 err = new BufferedReader(new InputStreamReader(errStream, "UTF-8"));
210 while ((line = err.readLine()) != null) {
211 sb.append(line).append("\n");
212 }
213 } catch (Exception ex) {
214 Main.error(e);
215 Main.error(ex);
216 } finally {
217 Utils.close(err);
218 }
219 }
220 final String msg = e.getMessage();
221 final String details = sb.toString();
222 Main.error(details.isEmpty() ? msg : msg + " - Details:\n" + details);
223
224 GuiHelper.runInEDTAndWait(new Runnable() {
225 @Override public void run() {
226 JPanel panel = new JPanel(new GridBagLayout());
227 panel.add(new JLabel(tr("JOSM failed to download plugin list:")), GBC.eol().insets(0, 0, 0, 10));
228 StringBuilder b = new StringBuilder();
229 for (String part : msg.split("(?<=\\G.{200})")) {
230 b.append(part).append("\n");
231 }
232 panel.add(new JLabel("<html><body width=\"500\"><b>"+b.toString().trim()+"</b></body></html>"), GBC.eol().insets(0, 0, 0, 10));
233 if (!details.isEmpty()) {
234 panel.add(new JLabel(tr("Details:")), GBC.eol().insets(0, 0, 0, 10));
235 JosmTextArea area = new JosmTextArea(details);
236 area.setEditable(false);
237 area.setLineWrap(true);
238 area.setWrapStyleWord(true);
239 JScrollPane scrollPane = new JScrollPane(area);
240 scrollPane.setPreferredSize(new Dimension(500, 300));
241 panel.add(scrollPane, GBC.eol().fill());
242 }
243 JOptionPane.showMessageDialog(monitor.getWindowParent(), panel, title, JOptionPane.ERROR_MESSAGE);
244 }
245 });
246 }
247
248 /**
249 * Downloads the icon archive from a remote location
250 *
251 * @param site the site URL
252 * @param monitor a progress monitor
253 */
254 protected void downloadPluginIcons(String site, File destFile, ProgressMonitor monitor) {
255 InputStream in = null;
256 OutputStream out = null;
257 try {
258 site = site.replaceAll("%<(.*)>", "");
259
260 monitor.beginTask("");
261 monitor.indeterminateSubTask(tr("Downloading plugin list from ''{0}''", site));
262
263 URL url = new URL(site);
264 synchronized(this) {
265 connection = Utils.openHttpConnection(url);
266 connection.setRequestProperty("Cache-Control", "no-cache");
267 }
268 in = connection.getInputStream();
269 out = new FileOutputStream(destFile);
270 byte[] buffer = new byte[8192];
271 for (int read = in.read(buffer); read != -1; read = in.read(buffer)) {
272 out.write(buffer, 0, read);
273 }
274 } catch (MalformedURLException e) {
275 if (canceled) return;
276 e.printStackTrace();
277 return;
278 } catch (IOException e) {
279 if (canceled) return;
280 handleIOException(monitor, e, tr("Plugin icons download error"), tr("JOSM failed to download plugin icons:"));
281 return;
282 } finally {
283 Utils.close(out);
284 synchronized(this) {
285 if (connection != null) {
286 connection.disconnect();
287 }
288 connection = null;
289 }
290 Utils.close(in);
291 monitor.finishTask();
292 }
293 for (PluginInformation pi : availablePlugins) {
294 if (pi.icon == null && pi.iconPath != null) {
295 pi.icon = new ImageProvider(pi.name+".jar/"+pi.iconPath)
296 .setArchive(destFile)
297 .setMaxWidth(24)
298 .setMaxHeight(24)
299 .setOptional(true).get();
300 }
301 }
302 }
303
304 /**
305 * Writes the list of plugins to a cache file
306 *
307 * @param site the site from where the list was downloaded
308 * @param list the downloaded list
309 */
310 protected void cachePluginList(String site, String list) {
311 PrintWriter writer = null;
312 try {
313 File pluginDir = Main.pref.getPluginsDirectory();
314 if (!pluginDir.exists()) {
315 if (! pluginDir.mkdirs()) {
316 Main.warn(tr("Failed to create plugin directory ''{0}''. Cannot cache plugin list from plugin site ''{1}''.", pluginDir.toString(), site));
317 }
318 }
319 File cacheFile = createSiteCacheFile(pluginDir, site, CacheType.PLUGIN_LIST);
320 getProgressMonitor().subTask(tr("Writing plugin list to local cache ''{0}''", cacheFile.toString()));
321 writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(cacheFile), "utf-8"));
322 writer.write(list);
323 } catch(IOException e) {
324 // just failed to write the cache file. No big deal, but log the exception anyway
325 e.printStackTrace();
326 } finally {
327 if (writer != null) {
328 writer.flush();
329 Utils.close(writer);
330 }
331 }
332 }
333
334 /**
335 * Filter information about deprecated plugins from the list of downloaded
336 * plugins
337 *
338 * @param plugins the plugin informations
339 * @return the plugin informations, without deprecated plugins
340 */
341 protected List<PluginInformation> filterDeprecatedPlugins(List<PluginInformation> plugins) {
342 List<PluginInformation> ret = new ArrayList<PluginInformation>(plugins.size());
343 HashSet<String> deprecatedPluginNames = new HashSet<String>();
344 for (PluginHandler.DeprecatedPlugin p : PluginHandler.DEPRECATED_PLUGINS) {
345 deprecatedPluginNames.add(p.name);
346 }
347 for (PluginInformation plugin: plugins) {
348 if (deprecatedPluginNames.contains(plugin.name)) {
349 continue;
350 }
351 ret.add(plugin);
352 }
353 return ret;
354 }
355
356 /**
357 * Parses the plugin list
358 *
359 * @param site the site from where the list was downloaded
360 * @param doc the document with the plugin list
361 */
362 protected void parsePluginListDocument(String site, String doc) {
363 try {
364 getProgressMonitor().subTask(tr("Parsing plugin list from site ''{0}''", site));
365 InputStream in = new ByteArrayInputStream(doc.getBytes("UTF-8"));
366 List<PluginInformation> pis = new PluginListParser().parse(in);
367 availablePlugins.addAll(filterDeprecatedPlugins(pis));
368 } catch (UnsupportedEncodingException e) {
369 Main.error(tr("Failed to parse plugin list document from site ''{0}''. Skipping site. Exception was: {1}", site, e.toString()));
370 e.printStackTrace();
371 } catch (PluginListParseException e) {
372 Main.error(tr("Failed to parse plugin list document from site ''{0}''. Skipping site. Exception was: {1}", site, e.toString()));
373 e.printStackTrace();
374 }
375 }
376
377 @Override
378 protected void realRun() throws SAXException, IOException, OsmTransferException {
379 if (sites == null) return;
380 getProgressMonitor().setTicksCount(sites.size() * 3);
381 File pluginDir = Main.pref.getPluginsDirectory();
382
383 // collect old cache files and remove if no longer in use
384 List<File> siteCacheFiles = new LinkedList<File>();
385 for (String location : PluginInformation.getPluginLocations()) {
386 File [] f = new File(location).listFiles(
387 new FilenameFilter() {
388 @Override
389 public boolean accept(File dir, String name) {
390 return name.matches("^([0-9]+-)?site.*\\.txt$") ||
391 name.matches("^([0-9]+-)?site.*-icons\\.zip$");
392 }
393 }
394 );
395 if(f != null && f.length > 0) {
396 siteCacheFiles.addAll(Arrays.asList(f));
397 }
398 }
399
400 for (String site: sites) {
401 String printsite = site.replaceAll("%<(.*)>", "");
402 getProgressMonitor().subTask(tr("Processing plugin list from site ''{0}''", printsite));
403 String list = downloadPluginList(site, getProgressMonitor().createSubTaskMonitor(0, false));
404 if (canceled) return;
405 siteCacheFiles.remove(createSiteCacheFile(pluginDir, site, CacheType.PLUGIN_LIST));
406 siteCacheFiles.remove(createSiteCacheFile(pluginDir, site, CacheType.ICON_LIST));
407 if(list != null)
408 {
409 getProgressMonitor().worked(1);
410 cachePluginList(site, list);
411 if (canceled) return;
412 getProgressMonitor().worked(1);
413 parsePluginListDocument(site, list);
414 if (canceled) return;
415 getProgressMonitor().worked(1);
416 if (canceled) return;
417 }
418 downloadPluginIcons(site+"-icons.zip", createSiteCacheFile(pluginDir, site, CacheType.ICON_LIST), getProgressMonitor().createSubTaskMonitor(0, false));
419 }
420 for (File file: siteCacheFiles) /* remove old stuff or whole update process is broken */
421 {
422 file.delete();
423 }
424 }
425
426 /**
427 * Replies true if the task was canceled
428 * @return <code>true</code> if the task was stopped by the user
429 */
430 public boolean isCanceled() {
431 return canceled;
432 }
433
434 /**
435 * Replies the list of plugins described in the downloaded plugin lists
436 *
437 * @return the list of plugins
438 * @since 5601
439 */
440 public List<PluginInformation> getAvailablePlugins() {
441 return availablePlugins;
442 }
443}
Note: See TracBrowser for help on using the repository browser.