source: josm/trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java@ 2924

Last change on this file since 2924 was 2924, checked in by Gubaer, 14 years ago

Fixed automatic update policy for plugins.
new: policy for version and time based automatic update can be configured in the preferences, see help

File size: 38.1 KB
Line 
1package org.openstreetmap.josm.plugins;
2
3import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Font;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.Window;
12import java.awt.event.ActionEvent;
13import java.io.File;
14import java.io.FilenameFilter;
15import java.net.URL;
16import java.net.URLClassLoader;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.HashMap;
23import java.util.HashSet;
24import java.util.Iterator;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29import java.util.Map.Entry;
30import java.util.concurrent.ExecutionException;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Executors;
33import java.util.concurrent.Future;
34
35import javax.swing.AbstractAction;
36import javax.swing.BorderFactory;
37import javax.swing.Box;
38import javax.swing.JButton;
39import javax.swing.JCheckBox;
40import javax.swing.JLabel;
41import javax.swing.JOptionPane;
42import javax.swing.JPanel;
43import javax.swing.JScrollPane;
44import javax.swing.JTextArea;
45import javax.swing.UIManager;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.data.Version;
49import org.openstreetmap.josm.gui.HelpAwareOptionPane;
50import org.openstreetmap.josm.gui.JMultilineLabel;
51import org.openstreetmap.josm.gui.MapFrame;
52import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
53import org.openstreetmap.josm.gui.download.DownloadSelection;
54import org.openstreetmap.josm.gui.help.HelpUtil;
55import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
56import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
57import org.openstreetmap.josm.gui.progress.ProgressMonitor;
58import org.openstreetmap.josm.tools.CheckParameterUtil;
59import org.openstreetmap.josm.tools.GBC;
60import org.openstreetmap.josm.tools.ImageProvider;
61
62/**
63 * PluginHandler is basically a collection of static utility functions used to bootstrap
64 * and manage the loaded plugins.
65 *
66 */
67public class PluginHandler {
68
69 final public static String [] DEPRECATED_PLUGINS = new String[] {"mappaint", "unglueplugin",
70 "lang-de", "lang-en_GB", "lang-fr", "lang-it", "lang-pl", "lang-ro",
71 "lang-ru", "ewmsplugin", "ywms", "tways-0.2", "geotagged", "landsat",
72 "namefinder", "waypoints", "slippy_map_chooser", "tcx-support", "usertools",
73 "AgPifoJ", "utilsplugin"};
74
75 final public static String [] UNMAINTAINED_PLUGINS = new String[] {"gpsbabelgui", "Intersect_way"};
76
77 /**
78 * All installed and loaded plugins (resp. their main classes)
79 */
80 public final static Collection<PluginProxy> pluginList = new LinkedList<PluginProxy>();
81
82
83 /**
84 * Removes deprecated plugins from a collection of plugins. Modifies the
85 * collection <code>plugins</code>.
86 *
87 * Also notifies the user about removed deprecated plugins
88 *
89 * @param plugins the collection of plugins
90 */
91 private static void filterDeprecatedPlugins(Window parent, Collection<String> plugins) {
92 Set<String> removedPlugins = new HashSet<String>();
93 for (String p : DEPRECATED_PLUGINS) {
94 if (plugins.contains(p)) {
95 plugins.remove(p);
96 Main.pref.removeFromCollection("plugins", p);
97 removedPlugins.add(p);
98 }
99 }
100 if (removedPlugins.isEmpty())
101 return;
102
103 // notify user about removed deprecated plugins
104 //
105 StringBuffer sb = new StringBuffer();
106 sb.append("<html>");
107 sb.append(trn(
108 "The following plugin is no longer necessary and has been deactivated:",
109 "The following plugins are no longer necessary and have been deactivated:",
110 removedPlugins.size()
111 ));
112 sb.append("<ul>");
113 for (String name: removedPlugins) {
114 sb.append("<li>").append(name).append("</li>");
115 }
116 sb.append("</ul>");
117 sb.append("</html>");
118 JOptionPane.showMessageDialog(
119 parent,
120 sb.toString(),
121 tr("Warning"),
122 JOptionPane.WARNING_MESSAGE
123 );
124 }
125
126 /**
127 * Removes unmaintained plugins from a collection of plugins. Modifies the
128 * collection <code>plugins</code>. Also removes the plugin from the list
129 * of plugins in the preferences, if necessary.
130 *
131 * Asks the user for every unmaintained plugin whether it should be removed.
132 *
133 * @param plugins the collection of plugins
134 */
135 private static void filterUnmaintainedPlugins(Window parent, Collection<String> plugins) {
136 for (String unmaintained : UNMAINTAINED_PLUGINS) {
137 if (!plugins.contains(unmaintained)) {
138 continue;
139 }
140 String msg = tr("<html>Loading of the plugin \"{0}\" was requested."
141 + "<br>This plugin is no longer developed and very likely will produce errors."
142 +"<br>It should be disabled.<br>Delete from preferences?</html>", unmaintained);
143 if (confirmDisablePlugin(parent, msg,unmaintained)) {
144 Main.pref.removeFromCollection("plugins", unmaintained);
145 plugins.remove(unmaintained);
146 }
147 }
148 }
149
150 /**
151 * Checks whether the locally available plugins should be updated and
152 * asks the user if running an update is OK. An update is advised if
153 * JOSM was updated to a new version since the last plugin updates or
154 * if the plugins were last updated a long time ago.
155 *
156 * @param parent the parent window relative to which the confirmation dialog
157 * is to be displayed
158 * @return true if a plugin update should be run; false, otherwise
159 */
160 public static boolean checkAndConfirmPluginUpdate(Window parent) {
161 String message = null;
162 String togglePreferenceKey = null;
163 int v = Version.getInstance().getVersion();
164 if (Main.pref.getInteger("pluginmanager.version", 0) < v) {
165 message =
166 "<html>"
167 + tr("You updated your JOSM software.<br>"
168 + "To prevent problems the plugins should be updated as well.<br><br>"
169 + "Update plugins now?"
170 )
171 + "</html>";
172 togglePreferenceKey = "pluginmanager.version-based-update.policy";
173 } else {
174 long tim = System.currentTimeMillis();
175 long last = Main.pref.getLong("pluginmanager.lastupdate", 0);
176 Integer maxTime = Main.pref.getInteger("pluginmanager.time-based-update.interval", 60);
177 long d = (tim - last) / (24 * 60 * 60 * 1000l);
178 if ((last <= 0) || (maxTime <= 0)) {
179 Main.pref.put("pluginmanager.lastupdate", Long.toString(tim));
180 } else if (d > maxTime) {
181 message =
182 "<html>"
183 + tr("Last plugin update more than {0} days ago.", d)
184 + "</html>";
185 togglePreferenceKey = "pluginmanager.time-based-update.policy";
186 }
187 }
188 if (message == null) return false;
189
190 ButtonSpec [] options = new ButtonSpec[] {
191 new ButtonSpec(
192 tr("Update plugins"),
193 ImageProvider.get("dialogs", "refresh"),
194 tr("Click to update the activated plugins"),
195 null /* no specific help context */
196 ),
197 new ButtonSpec(
198 tr("Skip update"),
199 ImageProvider.get("cancel"),
200 tr("Click to skip updating the activated plugins"),
201 null /* no specific help context */
202 )
203 };
204
205 UpdatePluginsMessagePanel pnlMessage = new UpdatePluginsMessagePanel();
206 pnlMessage.setMessage(message);
207 pnlMessage.initDontShowAgain(togglePreferenceKey);
208
209 // check whether automatic update at startup was disabled
210 //
211 String policy = Main.pref.get(togglePreferenceKey, "ask");
212 policy = policy.trim().toLowerCase();
213 if (policy.equals("never")) {
214 if (togglePreferenceKey.equals("pluginmanager.version-based-update.policy")) {
215 System.out.println(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled."));
216 } else if (togglePreferenceKey.equals("pluginmanager.time-based-update.policy")) {
217 System.out.println(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled."));
218 }
219 return false;
220 }
221
222 if (policy.equals("always")) {
223 if (togglePreferenceKey.equals("pluginmanager.time-based-update.policy")) {
224 System.out.println(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled."));
225 } else if (togglePreferenceKey.equals("pluginmanager.time-based-update.policy")) {
226 System.out.println(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled."));
227 }
228 return true;
229 }
230
231 if (!policy.equals("ask")) {
232 System.err.println(tr("Unexpected value ''{0}'' for preference ''{1}''. Assuming value ''ask''.", policy, togglePreferenceKey));
233 }
234 int ret = HelpAwareOptionPane.showOptionDialog(
235 parent,
236 pnlMessage,
237 tr("Update plugins"),
238 JOptionPane.WARNING_MESSAGE,
239 null,
240 options,
241 options[0],
242 ht("/Preferences/Plugins#AutomaticUpdate")
243 );
244
245 if (pnlMessage.isRememberDecision()) {
246 switch(ret) {
247 case 0:
248 Main.pref.put(togglePreferenceKey, "always");
249 break;
250 case JOptionPane.CLOSED_OPTION:
251 case 1:
252 Main.pref.put(togglePreferenceKey, "never");
253 break;
254 }
255 } else {
256 Main.pref.put(togglePreferenceKey, "ask");
257 }
258 return ret == 0;
259 }
260
261 /**
262 * Alerts the user if a plugin required by another plugin is missing
263 *
264 * @param plugin the the plugin
265 * @param missingRequiredPlugin the missing required plugin
266 */
267 private static void alertMissingRequiredPlugin(Window parent, String plugin, Set<String> missingRequiredPlugin) {
268 StringBuilder sb = new StringBuilder();
269 sb.append("<html>");
270 sb.append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:",
271 "Plugin {0} requires {1} plugins which were not found. The missing plugins are:",
272 missingRequiredPlugin.size(),
273 plugin,
274 missingRequiredPlugin.size()
275 ));
276 sb.append("<ul>");
277 for (String p: missingRequiredPlugin) {
278 sb.append("<li>").append(p).append("</li>");
279 }
280 sb.append("</ul>").append("</html>");
281 JOptionPane.showMessageDialog(
282 parent,
283 sb.toString(),
284 tr("Error"),
285 JOptionPane.ERROR_MESSAGE
286 );
287 }
288
289 private static void alertJOSMUpdateRequired(Window parent, String plugin, int requiredVersion) {
290 HelpAwareOptionPane.showOptionDialog(
291 parent,
292 tr("<html>Plugin {0} requires JOSM version {1}. The current JOSM version is {2}.<br>"
293 +"You have to update JOSM in order to use this plugin.</html>",
294 plugin, requiredVersion, Version.getInstance().getVersion()
295 ),
296 tr("Warning"),
297 JOptionPane.WARNING_MESSAGE,
298 HelpUtil.ht("/Plugin/Loading#JOSMUpdateRequired")
299 );
300 }
301
302 /**
303 * Checks whether all preconditions for loading the plugin <code>plugin</code> are met. The
304 * current JOSM version must be compatible with the plugin and no other plugins this plugin
305 * depends on should be missing.
306 *
307 * @param plugins the collection of all loaded plugins
308 * @param plugin the plugin for which preconditions are checked
309 * @return true, if the preconditions are met; false otherwise
310 */
311 public static boolean checkLoadPreconditions(Window parent, Collection<PluginInformation> plugins, PluginInformation plugin) {
312
313 // make sure the plugin is compatible with the current JOSM version
314 //
315 int josmVersion = Version.getInstance().getVersion();
316 if (plugin.mainversion > josmVersion && josmVersion != Version.JOSM_UNKNOWN_VERSION) {
317 alertJOSMUpdateRequired(parent, plugin.name, plugin.mainversion);
318 return false;
319 }
320
321 // make sure the dependencies to other plugins are not broken
322 //
323 if(plugin.requires != null){
324 Set<String> pluginNames = new HashSet<String>();
325 for (PluginInformation pi: plugins) {
326 pluginNames.add(pi.name);
327 }
328 Set<String> missingPlugins = new HashSet<String>();
329 for (String requiredPlugin : plugin.requires.split(";")) {
330 if (!pluginNames.contains(requiredPlugin)) {
331 missingPlugins.add(requiredPlugin);
332 }
333 }
334 if (!missingPlugins.isEmpty()) {
335 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
336 return false;
337 }
338 }
339 return true;
340 }
341
342 /**
343 * Creates a class loader for loading plugin code.
344 *
345 * @param plugins the collection of plugins which are going to be loaded with this
346 * class loader
347 * @return the class loader
348 */
349 public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
350 // iterate all plugins and collect all libraries of all plugins:
351 List<URL> allPluginLibraries = new LinkedList<URL>();
352 File pluginDir = Main.pref.getPluginsDirectory();
353 for (PluginInformation info : plugins) {
354 if (info.libraries == null) {
355 continue;
356 }
357 allPluginLibraries.addAll(info.libraries);
358 File pluginJar = new File(pluginDir, info.name + ".jar");
359 URL pluginJarUrl = PluginInformation.fileToURL(pluginJar);
360 allPluginLibraries.add(pluginJarUrl);
361 }
362
363 // create a classloader for all plugins:
364 URL[] jarUrls = new URL[allPluginLibraries.size()];
365 jarUrls = allPluginLibraries.toArray(jarUrls);
366 URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
367 return pluginClassLoader;
368 }
369
370 /**
371 * Loads and instantiates the plugin described by <code>plugin</code> using
372 * the class loader <code>pluginClassLoader</code>.
373 *
374 * @param plugin the plugin
375 * @param pluginClassLoader the plugin class loader
376 */
377 public static void loadPlugin(Window parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
378 try {
379 Class<?> klass = plugin.loadClass(pluginClassLoader);
380 if (klass != null) {
381 System.out.println(tr("loading plugin ''{0}''", plugin.name));
382 pluginList.add(plugin.load(klass));
383 }
384 } catch (Throwable e) {
385 e.printStackTrace();
386 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
387 if (confirmDisablePlugin(parent, msg, plugin.name)) {
388 Main.pref.removeFromCollection("plugins", plugin.name);
389 }
390 }
391 }
392
393 /**
394 * Loads the plugin in <code>plugins</code> from locally available jar files into
395 * memory.
396 *
397 * @param plugins the list of plugins
398 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
399 */
400 public static void loadPlugins(Window parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
401 if (monitor == null) {
402 monitor = NullProgressMonitor.INSTANCE;
403 }
404 try {
405 monitor.beginTask(tr("Loading plugins ..."));
406 monitor.subTask(tr("Checking plugin preconditions..."));
407 List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
408 for (PluginInformation pi: plugins) {
409 if (checkLoadPreconditions(parent, plugins, pi)) {
410 toLoad.add(pi);
411 }
412 }
413 // sort the plugins according to their "staging" equivalence class. The
414 // lower the value of "stage" the earlier the plugin should be loaded.
415 //
416 Collections.sort(
417 toLoad,
418 new Comparator<PluginInformation>() {
419 public int compare(PluginInformation o1, PluginInformation o2) {
420 if (o1.stage < o2.stage) return -1;
421 if (o1.stage == o2.stage) return 0;
422 return 1;
423 }
424 }
425 );
426 if (toLoad.isEmpty())
427 return;
428
429 ClassLoader pluginClassLoader = createClassLoader(toLoad);
430 ImageProvider.sources.add(0, pluginClassLoader);
431 monitor.setTicksCount(toLoad.size());
432 for (PluginInformation info : toLoad) {
433 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
434 loadPlugin(parent, info, pluginClassLoader);
435 monitor.worked(1);
436 }
437 } finally {
438 monitor.finishTask();
439 }
440 }
441
442 /**
443 * Loads plugins from <code>plugins</code> which have the flag {@see PluginInformation#early}
444 * set to true.
445 *
446 * @param plugins the collection of plugins
447 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
448 */
449 public static void loadEarlyPlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
450 List<PluginInformation> earlyPlugins = new ArrayList<PluginInformation>(plugins.size());
451 for (PluginInformation pi: plugins) {
452 if (pi.early) {
453 earlyPlugins.add(pi);
454 }
455 }
456 loadPlugins(parent, earlyPlugins, monitor);
457 }
458
459 /**
460 * Loads plugins from <code>plugins</code> which have the flag {@see PluginInformation#early}
461 * set to false.
462 *
463 * @param plugins the collection of plugins
464 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
465 */
466 public static void loadLatePlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
467 List<PluginInformation> latePlugins = new ArrayList<PluginInformation>(plugins.size());
468 for (PluginInformation pi: plugins) {
469 if (!pi.early) {
470 latePlugins.add(pi);
471 }
472 }
473 loadPlugins(parent, latePlugins, monitor);
474 }
475
476 /**
477 * Loads locally available plugin information from local plugin jars and from cached
478 * plugin lists.
479 *
480 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
481 * @return the list of locally available plugin information
482 *
483 */
484 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
485 if (monitor == null) {
486 monitor = NullProgressMonitor.INSTANCE;
487 }
488 try {
489 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
490 ExecutorService service = Executors.newSingleThreadExecutor();
491 Future<?> future = service.submit(task);
492 try {
493 future.get();
494 } catch(ExecutionException e) {
495 e.printStackTrace();
496 return null;
497 } catch(InterruptedException e) {
498 e.printStackTrace();
499 return null;
500 }
501 HashMap<String, PluginInformation> ret = new HashMap<String, PluginInformation>();
502 for (PluginInformation pi: task.getAvailablePlugins()) {
503 ret.put(pi.name, pi);
504 }
505 return ret;
506 } finally {
507 monitor.finishTask();
508 }
509 }
510
511 private static void alertMissingPluginInformation(Window parent, Collection<String> plugins) {
512 StringBuilder sb = new StringBuilder();
513 sb.append("<html>");
514 sb.append(trn("JOSM could not find information about the following plugin:",
515 "JOSM could not find information about the following plugins:",
516 plugins.size()));
517 sb.append("<ul>");
518 for (String plugin: plugins) {
519 sb.append("<li>").append(plugin).append("</li>");
520 }
521 sb.append("</ul>");
522 sb.append(trn("The plugin is not going to be loaded.",
523 "The plugins are not going to be loaded.",
524 plugins.size()));
525 sb.append("</html>");
526 HelpAwareOptionPane.showOptionDialog(
527 parent,
528 sb.toString(),
529 tr("Warning"),
530 JOptionPane.WARNING_MESSAGE,
531 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
532 );
533 }
534
535 /**
536 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
537 * out. This involves user interaction. This method displays alert and confirmation
538 * messages.
539 *
540 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
541 * @return the set of plugins to load (as set of plugin names)
542 */
543 public static List<PluginInformation> buildListOfPluginsToLoad(Window parent, ProgressMonitor monitor) {
544 if (monitor == null) {
545 monitor = NullProgressMonitor.INSTANCE;
546 }
547 try {
548 monitor.beginTask(tr("Determine plugins to load..."));
549 Set<String> plugins = new HashSet<String>();
550 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
551 if (System.getProperty("josm.plugins") != null) {
552 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
553 }
554 monitor.subTask(tr("Removing deprecated plugins..."));
555 filterDeprecatedPlugins(parent, plugins);
556 monitor.subTask(tr("Removing umaintained plugins..."));
557 filterUnmaintainedPlugins(parent, plugins);
558 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
559 List<PluginInformation> ret = new LinkedList<PluginInformation>();
560 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
561 String plugin = it.next();
562 if (infos.containsKey(plugin)) {
563 ret.add(infos.get(plugin));
564 it.remove();
565 }
566 }
567 if (!plugins.isEmpty()) {
568 alertMissingPluginInformation(parent, plugins);
569 }
570 return ret;
571 } finally {
572 monitor.finishTask();
573 }
574 }
575
576 private static void alertFailedPluginUpdate(Window parent, Collection<PluginInformation> plugins) {
577 StringBuffer sb = new StringBuffer();
578 sb.append("<html>");
579 sb.append(trn(
580 "Updating the following plugin has failed:",
581 "Updating the following plugins has failed:",
582 plugins.size()
583 )
584 );
585 sb.append("<ul>");
586 for (PluginInformation pi: plugins) {
587 sb.append("<li>").append(pi.name).append("</li>");
588 }
589 sb.append("</ul>");
590 sb.append(trn(
591 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
592 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
593 plugins.size()
594 ));
595 sb.append("</html>");
596 HelpAwareOptionPane.showOptionDialog(
597 parent,
598 sb.toString(),
599 tr("Plugin update failed"),
600 JOptionPane.ERROR_MESSAGE,
601 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
602 );
603 }
604
605 /**
606 * Updates the plugins in <code>plugins</code>.
607 *
608 * @param plugins the collection of plugins to update. Must not be null.
609 * @param monitor the progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null.
610 * @throws IllegalArgumentException thrown if plugins is null
611 */
612 public static void updatePlugins(Window parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) throws IllegalArgumentException{
613 CheckParameterUtil.ensureParameterNotNull(plugins, "plugins");
614 if (monitor == null) {
615 monitor = NullProgressMonitor.INSTANCE;
616 }
617 try {
618 PluginDownloadTask task = new PluginDownloadTask(
619 monitor,
620 plugins,
621 tr("Update plugins")
622 );
623 ExecutorService service = Executors.newSingleThreadExecutor();
624 Future<?> future = service.submit(task);
625 try {
626 future.get();
627 } catch(ExecutionException e) {
628 e.printStackTrace();
629 } catch(InterruptedException e) {
630 e.printStackTrace();
631 }
632 if (! task.getFailedPlugins().isEmpty()) {
633 alertFailedPluginUpdate(parent, task.getFailedPlugins());
634 return;
635 }
636 } finally {
637 monitor.finishTask();
638 }
639 // remember the update because it was successful
640 //
641 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
642 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
643 }
644
645 /**
646 * Ask the user for confirmation that a plugin shall be disabled.
647 *
648 * @param reason the reason for disabling the plugin
649 * @param name the plugin name
650 * @return true, if the plugin shall be disabled; false, otherwise
651 */
652 public static boolean confirmDisablePlugin(Window parent, String reason, String name) {
653 ButtonSpec [] options = new ButtonSpec[] {
654 new ButtonSpec(
655 tr("Disable plugin"),
656 ImageProvider.get("dialogs", "delete"),
657 tr("Click to delete the plugin ''{0}''", name),
658 null /* no specific help context */
659 ),
660 new ButtonSpec(
661 tr("Keep plugin"),
662 ImageProvider.get("cancel"),
663 tr("Click to keep the plugin ''{0}''", name),
664 null /* no specific help context */
665 )
666 };
667 int ret = HelpAwareOptionPane.showOptionDialog(
668 parent,
669 reason,
670 tr("Disable plugin"),
671 JOptionPane.WARNING_MESSAGE,
672 null,
673 options,
674 options[0],
675 null // FIXME: add help topic
676 );
677 return ret == 0;
678 }
679
680 /**
681 * Notified loaded plugins about a new map frame
682 *
683 * @param old the old map frame
684 * @param map the new map frame
685 */
686 public static void notifyMapFrameChanged(MapFrame old, MapFrame map) {
687 for (PluginProxy plugin : pluginList) {
688 plugin.mapFrameInitialized(old, map);
689 }
690 }
691
692 public static Object getPlugin(String name) {
693 for (PluginProxy plugin : pluginList)
694 if(plugin.getPluginInformation().name.equals(name))
695 return plugin.plugin;
696 return null;
697 }
698
699 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
700 for (PluginProxy p : pluginList) {
701 p.addDownloadSelection(downloadSelections);
702 }
703 }
704
705 public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
706 for (PluginProxy plugin : pluginList) {
707 settings.add(new PluginPreferenceFactory(plugin));
708 }
709 }
710
711 /**
712 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
713 * ".jar" files.
714 *
715 */
716 public static void installDownloadedPlugins() {
717 File pluginDir = Main.pref.getPluginsDirectory();
718 if (! pluginDir.exists() || ! pluginDir.isDirectory() || ! pluginDir.canWrite())
719 return;
720
721 final File[] files = pluginDir.listFiles(new FilenameFilter() {
722 public boolean accept(File dir, String name) {
723 return name.endsWith(".jar.new");
724 }});
725
726 for (File updatedPlugin : files) {
727 final String filePath = updatedPlugin.getPath();
728 File plugin = new File(filePath.substring(0, filePath.length() - 4));
729 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
730 if (plugin.exists()) {
731 if (!plugin.delete()) {
732 System.err.println(tr("Warning: failed to delete outdated plugin ''{0}''.", plugin.toString()));
733 System.err.println(tr("Warning: failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM still going to load the old plugin version.", pluginName));
734 continue;
735 }
736 }
737 if (!updatedPlugin.renameTo(plugin)) {
738 System.err.println(tr("Warning: failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.", plugin.toString(), updatedPlugin.toString()));
739 System.err.println(tr("Warning: failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM still going to load the old plugin version.", pluginName));
740 }
741 }
742 return;
743 }
744
745 private static boolean confirmDeactivatingPluginAfterException(PluginProxy plugin) {
746 ButtonSpec [] options = new ButtonSpec[] {
747 new ButtonSpec(
748 tr("Disable plugin"),
749 ImageProvider.get("dialogs", "delete"),
750 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
751 null /* no specific help context */
752 ),
753 new ButtonSpec(
754 tr("Keep plugin"),
755 ImageProvider.get("cancel"),
756 tr("Click to keep the plugin ''{0}''",plugin.getPluginInformation().name),
757 null /* no specific help context */
758 )
759 };
760
761 StringBuffer msg = new StringBuffer();
762 msg.append("<html>");
763 msg.append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.getPluginInformation().name));
764 msg.append("<br>");
765 if(plugin.getPluginInformation().author != null) {
766 msg.append(tr("According to the information within the plugin, the author is {0}.", plugin.getPluginInformation().author));
767 msg.append("<br>");
768 }
769 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."));
770 msg.append("<br>");
771 msg.append(tr("Should the plugin be disabled?"));
772 msg.append("</html>");
773
774 int ret = HelpAwareOptionPane.showOptionDialog(
775 Main.parent,
776 msg.toString(),
777 tr("Update plugins"),
778 JOptionPane.QUESTION_MESSAGE,
779 null,
780 options,
781 options[0],
782 ht("/ErrorMessages#ErrorInPlugin")
783 );
784 return ret == 0;
785 }
786
787 /**
788 * Replies the plugin which most likely threw the exception <code>ex</code>.
789 *
790 * @param ex the exception
791 * @return the plugin; null, if the exception proably wasn't thrown from a plugin
792 */
793 private static PluginProxy getPluginCausingException(Throwable ex) {
794 for (PluginProxy p : pluginList) {
795 String baseClass = p.getPluginInformation().className;
796 int i = baseClass.lastIndexOf(".");
797 baseClass = baseClass.substring(0, i);
798 for (StackTraceElement element : ex.getStackTrace()) {
799 String c = element.getClassName();
800 if (c.startsWith(baseClass))
801 return p;
802 }
803 }
804 return null;
805 }
806
807 /**
808 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
809 * conditionally deactivates the plugin, but asks the user first.
810 *
811 * @param e the exception
812 */
813 public static void disablePluginAfterException(Throwable e) {
814 PluginProxy plugin = null;
815 // Check for an explicit problem when calling a plugin function
816 if (e instanceof PluginException) {
817 plugin = ((PluginException) e).plugin;
818 }
819 if (plugin == null) {
820 plugin = getPluginCausingException(e);
821 }
822 if (plugin == null)
823 // don't know what plugin threw the exception
824 return;
825
826 Set<String> plugins = new HashSet<String>(
827 Main.pref.getCollection("plugins",Collections.<String> emptySet())
828 );
829 if (! plugins.contains(plugin.getPluginInformation().name))
830 // plugin not activated ? strange in this context but anyway, don't bother
831 // the user with dialogs, skip conditional deactivation
832 return;
833
834 if (!confirmDeactivatingPluginAfterException(plugin))
835 // user doesn't want to deactivate the plugin
836 return;
837
838 // deactivate the plugin
839 plugins.remove(plugin.getPluginInformation().name);
840 Main.pref.putCollection("plugins", plugins);
841 JOptionPane.showMessageDialog(
842 Main.parent,
843 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
844 tr("Information"),
845 JOptionPane.INFORMATION_MESSAGE
846 );
847 return;
848 }
849
850 public static String getBugReportText() {
851 String text = "";
852 String pl = Main.pref.getCollectionAsString("plugins");
853 if (pl != null && pl.length() != 0) {
854 text += "Plugins: " + pl + "\n";
855 }
856 for (final PluginProxy pp : pluginList) {
857 text += "Plugin "
858 + pp.getPluginInformation().name
859 + (pp.getPluginInformation().version != null && !pp.getPluginInformation().version.equals("") ? " Version: " + pp.getPluginInformation().version + "\n"
860 : "\n");
861 }
862 return text;
863 }
864
865 public static JPanel getInfoPanel() {
866 JPanel pluginTab = new JPanel(new GridBagLayout());
867 for (final PluginProxy p : pluginList) {
868 final PluginInformation info = p.getPluginInformation();
869 String name = info.name
870 + (info.version != null && !info.version.equals("") ? " Version: " + info.version : "");
871 pluginTab.add(new JLabel(name), GBC.std());
872 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
873 pluginTab.add(new JButton(new AbstractAction(tr("Information")) {
874 public void actionPerformed(ActionEvent event) {
875 StringBuilder b = new StringBuilder();
876 for (Entry<String, String> e : info.attr.entrySet()) {
877 b.append(e.getKey());
878 b.append(": ");
879 b.append(e.getValue());
880 b.append("\n");
881 }
882 JTextArea a = new JTextArea(10, 40);
883 a.setEditable(false);
884 a.setText(b.toString());
885 JOptionPane.showMessageDialog(Main.parent, new JScrollPane(a), tr("Plugin information"),
886 JOptionPane.INFORMATION_MESSAGE);
887 }
888 }), GBC.eol());
889
890 JTextArea description = new JTextArea((info.description == null ? tr("no description available")
891 : info.description));
892 description.setEditable(false);
893 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
894 description.setLineWrap(true);
895 description.setWrapStyleWord(true);
896 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
897 description.setBackground(UIManager.getColor("Panel.background"));
898
899 pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL));
900 }
901 return pluginTab;
902 }
903
904 static private class UpdatePluginsMessagePanel extends JPanel {
905 private JMultilineLabel lblMessage;
906 private JCheckBox cbDontShowAgain;
907
908 protected void build() {
909 setLayout(new GridBagLayout());
910 GridBagConstraints gc = new GridBagConstraints();
911 gc.anchor = GridBagConstraints.NORTHWEST;
912 gc.fill = GridBagConstraints.BOTH;
913 gc.weightx = 1.0;
914 gc.weighty = 1.0;
915 gc.insets = new Insets(5,5,5,5);
916 add(lblMessage = new JMultilineLabel(""), gc);
917 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
918
919 gc.gridy = 1;
920 gc.fill = GridBagConstraints.HORIZONTAL;
921 gc.weighty = 0.0;
922 add(cbDontShowAgain = new JCheckBox(tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)")), gc);
923 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
924 }
925
926 public UpdatePluginsMessagePanel() {
927 build();
928 }
929
930 public void setMessage(String message) {
931 lblMessage.setText(message);
932 }
933
934 public void initDontShowAgain(String preferencesKey) {
935 String policy = Main.pref.get(preferencesKey, "ask");
936 policy = policy.trim().toLowerCase();
937 cbDontShowAgain.setSelected(! policy.equals("ask"));
938 }
939
940 public boolean isRememberDecision() {
941 return cbDontShowAgain.isSelected();
942 }
943 }
944}
Note: See TracBrowser for help on using the repository browser.