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

Last change on this file since 7012 was 7005, checked in by Don-vip, 10 years ago

see #8465 - use diamond operator where applicable

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