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

Last change on this file since 12187 was 11553, checked in by Don-vip, 7 years ago

refactor handling of null values - use Java 8 Optional where possible

  • Property svn:eol-style set to native
File size: 12.8 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.ByteArrayInputStream;
9import java.io.File;
10import java.io.FilenameFilter;
11import java.io.IOException;
12import java.io.InputStream;
13import java.io.PrintWriter;
14import java.net.MalformedURLException;
15import java.net.URL;
16import java.nio.charset.StandardCharsets;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Optional;
25import java.util.Set;
26
27import javax.swing.JLabel;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.gui.PleaseWaitRunnable;
34import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
35import org.openstreetmap.josm.gui.progress.ProgressMonitor;
36import org.openstreetmap.josm.gui.util.GuiHelper;
37import org.openstreetmap.josm.gui.widgets.JosmTextArea;
38import org.openstreetmap.josm.io.OsmTransferException;
39import org.openstreetmap.josm.tools.GBC;
40import org.openstreetmap.josm.tools.HttpClient;
41import org.openstreetmap.josm.tools.Utils;
42import org.xml.sax.SAXException;
43
44/**
45 * An asynchronous task for downloading plugin lists from the configured plugin download sites.
46 * @since 2817
47 */
48public class ReadRemotePluginInformationTask extends PleaseWaitRunnable {
49
50 private Collection<String> sites;
51 private boolean canceled;
52 private HttpClient connection;
53 private List<PluginInformation> availablePlugins;
54 private boolean displayErrMsg;
55
56 protected final void init(Collection<String> sites, boolean displayErrMsg) {
57 this.sites = Optional.ofNullable(sites).orElseGet(Collections::emptySet);
58 this.availablePlugins = new LinkedList<>();
59 this.displayErrMsg = displayErrMsg;
60 }
61
62 /**
63 * Constructs a new {@code ReadRemotePluginInformationTask}.
64 *
65 * @param sites the collection of download sites. Defaults to the empty collection if null.
66 */
67 public ReadRemotePluginInformationTask(Collection<String> sites) {
68 super(tr("Download plugin list..."), false /* don't ignore exceptions */);
69 init(sites, true);
70 }
71
72 /**
73 * Constructs a new {@code ReadRemotePluginInformationTask}.
74 *
75 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null
76 * @param sites the collection of download sites. Defaults to the empty collection if null.
77 * @param displayErrMsg if {@code true}, a blocking error message is displayed in case of I/O exception.
78 */
79 public ReadRemotePluginInformationTask(ProgressMonitor monitor, Collection<String> sites, boolean displayErrMsg) {
80 super(tr("Download plugin list..."), monitor == null ? NullProgressMonitor.INSTANCE : monitor, false /* don't ignore exceptions */);
81 init(sites, displayErrMsg);
82 }
83
84 @Override
85 protected void cancel() {
86 canceled = true;
87 synchronized (this) {
88 if (connection != null) {
89 connection.disconnect();
90 }
91 }
92 }
93
94 @Override
95 protected void finish() {
96 // Do nothing
97 }
98
99 /**
100 * Creates the file name for the cached plugin list and the icon cache file.
101 *
102 * @param pluginDir directory of plugin for data storage
103 * @param site the name of the site
104 * @return the file name for the cache file
105 */
106 protected File createSiteCacheFile(File pluginDir, String site) {
107 String name;
108 try {
109 site = site.replaceAll("%<(.*)>", "");
110 URL url = new URL(site);
111 StringBuilder sb = new StringBuilder();
112 sb.append("site-")
113 .append(url.getHost()).append('-');
114 if (url.getPort() != -1) {
115 sb.append(url.getPort()).append('-');
116 }
117 String path = url.getPath();
118 for (int i = 0; i < path.length(); i++) {
119 char c = path.charAt(i);
120 if (Character.isLetterOrDigit(c)) {
121 sb.append(c);
122 } else {
123 sb.append('_');
124 }
125 }
126 sb.append(".txt");
127 name = sb.toString();
128 } catch (MalformedURLException e) {
129 name = "site-unknown.txt";
130 }
131 return new File(pluginDir, name);
132 }
133
134 /**
135 * Downloads the list from a remote location
136 *
137 * @param site the site URL
138 * @param monitor a progress monitor
139 * @return the downloaded list
140 */
141 protected String downloadPluginList(String site, final ProgressMonitor monitor) {
142 /* replace %<x> with empty string or x=plugins (separated with comma) */
143 String pl = Utils.join(",", Main.pref.getCollection("plugins"));
144 String printsite = site.replaceAll("%<(.*)>", "");
145 if (pl != null && !pl.isEmpty()) {
146 site = site.replaceAll("%<(.*)>", "$1"+pl);
147 } else {
148 site = printsite;
149 }
150
151 String content = null;
152 try {
153 monitor.beginTask("");
154 monitor.indeterminateSubTask(tr("Downloading plugin list from ''{0}''", printsite));
155
156 URL url = new URL(site);
157 connection = HttpClient.create(url).useCache(false);
158 final HttpClient.Response response = connection.connect();
159 content = response.fetchContent();
160 if (response.getResponseCode() != 200) {
161 throw new IOException(tr("Unsuccessful HTTP request"));
162 }
163 return content;
164 } catch (MalformedURLException e) {
165 if (canceled) return null;
166 Main.error(e);
167 return null;
168 } catch (IOException e) {
169 if (canceled) return null;
170 handleIOException(monitor, e, content);
171 return null;
172 } finally {
173 synchronized (this) {
174 if (connection != null) {
175 connection.disconnect();
176 }
177 connection = null;
178 }
179 monitor.finishTask();
180 }
181 }
182
183 private void handleIOException(final ProgressMonitor monitor, IOException e, String details) {
184 final String msg = e.getMessage();
185 if (details == null || details.isEmpty()) {
186 Main.error(e.getClass().getSimpleName()+": " + msg);
187 } else {
188 Main.error(msg + " - Details:\n" + details);
189 }
190
191 if (displayErrMsg) {
192 displayErrorMessage(monitor, msg, details, tr("Plugin list download error"), tr("JOSM failed to download plugin list:"));
193 }
194 }
195
196 private static void displayErrorMessage(final ProgressMonitor monitor, final String msg, final String details, final String title,
197 final String firstMessage) {
198 GuiHelper.runInEDTAndWait(() -> {
199 JPanel panel = new JPanel(new GridBagLayout());
200 panel.add(new JLabel(firstMessage), GBC.eol().insets(0, 0, 0, 10));
201 StringBuilder b = new StringBuilder();
202 for (String part : msg.split("(?<=\\G.{200})")) {
203 b.append(part).append('\n');
204 }
205 panel.add(new JLabel("<html><body width=\"500\"><b>"+b.toString().trim()+"</b></body></html>"), GBC.eol().insets(0, 0, 0, 10));
206 if (details != null && !details.isEmpty()) {
207 panel.add(new JLabel(tr("Details:")), GBC.eol().insets(0, 0, 0, 10));
208 JosmTextArea area = new JosmTextArea(details);
209 area.setEditable(false);
210 area.setLineWrap(true);
211 area.setWrapStyleWord(true);
212 JScrollPane scrollPane = new JScrollPane(area);
213 scrollPane.setPreferredSize(new Dimension(500, 300));
214 panel.add(scrollPane, GBC.eol().fill());
215 }
216 JOptionPane.showMessageDialog(monitor.getWindowParent(), panel, title, JOptionPane.ERROR_MESSAGE);
217 });
218 }
219
220 /**
221 * Writes the list of plugins to a cache file
222 *
223 * @param site the site from where the list was downloaded
224 * @param list the downloaded list
225 */
226 protected void cachePluginList(String site, String list) {
227 File pluginDir = Main.pref.getPluginsDirectory();
228 if (!pluginDir.exists() && !pluginDir.mkdirs()) {
229 Main.warn(tr("Failed to create plugin directory ''{0}''. Cannot cache plugin list from plugin site ''{1}''.",
230 pluginDir.toString(), site));
231 }
232 File cacheFile = createSiteCacheFile(pluginDir, site);
233 getProgressMonitor().subTask(tr("Writing plugin list to local cache ''{0}''", cacheFile.toString()));
234 try (PrintWriter writer = new PrintWriter(cacheFile, StandardCharsets.UTF_8.name())) {
235 writer.write(list);
236 writer.flush();
237 } catch (IOException e) {
238 // just failed to write the cache file. No big deal, but log the exception anyway
239 Main.error(e);
240 }
241 }
242
243 /**
244 * Filter information about deprecated plugins from the list of downloaded
245 * plugins
246 *
247 * @param plugins the plugin informations
248 * @return the plugin informations, without deprecated plugins
249 */
250 protected List<PluginInformation> filterDeprecatedPlugins(List<PluginInformation> plugins) {
251 List<PluginInformation> ret = new ArrayList<>(plugins.size());
252 Set<String> deprecatedPluginNames = new HashSet<>();
253 for (PluginHandler.DeprecatedPlugin p : PluginHandler.DEPRECATED_PLUGINS) {
254 deprecatedPluginNames.add(p.name);
255 }
256 for (PluginInformation plugin: plugins) {
257 if (deprecatedPluginNames.contains(plugin.name)) {
258 continue;
259 }
260 ret.add(plugin);
261 }
262 return ret;
263 }
264
265 /**
266 * Parses the plugin list
267 *
268 * @param site the site from where the list was downloaded
269 * @param doc the document with the plugin list
270 */
271 protected void parsePluginListDocument(String site, String doc) {
272 try {
273 getProgressMonitor().subTask(tr("Parsing plugin list from site ''{0}''", site));
274 InputStream in = new ByteArrayInputStream(doc.getBytes(StandardCharsets.UTF_8));
275 List<PluginInformation> pis = new PluginListParser().parse(in);
276 availablePlugins.addAll(filterDeprecatedPlugins(pis));
277 } catch (PluginListParseException e) {
278 Main.error(tr("Failed to parse plugin list document from site ''{0}''. Skipping site. Exception was: {1}", site, e.toString()));
279 Main.error(e);
280 }
281 }
282
283 @Override
284 protected void realRun() throws SAXException, IOException, OsmTransferException {
285 if (sites == null) return;
286 getProgressMonitor().setTicksCount(sites.size() * 3);
287
288 // collect old cache files and remove if no longer in use
289 List<File> siteCacheFiles = new LinkedList<>();
290 for (String location : PluginInformation.getPluginLocations()) {
291 File[] f = new File(location).listFiles(
292 (FilenameFilter) (dir, name) -> name.matches("^([0-9]+-)?site.*\\.txt$") ||
293 name.matches("^([0-9]+-)?site.*-icons\\.zip$")
294 );
295 if (f != null && f.length > 0) {
296 siteCacheFiles.addAll(Arrays.asList(f));
297 }
298 }
299
300 File pluginDir = Main.pref.getPluginsDirectory();
301 for (String site: sites) {
302 String printsite = site.replaceAll("%<(.*)>", "");
303 getProgressMonitor().subTask(tr("Processing plugin list from site ''{0}''", printsite));
304 String list = downloadPluginList(site, getProgressMonitor().createSubTaskMonitor(0, false));
305 if (canceled) return;
306 siteCacheFiles.remove(createSiteCacheFile(pluginDir, site));
307 if (list != null) {
308 getProgressMonitor().worked(1);
309 cachePluginList(site, list);
310 if (canceled) return;
311 getProgressMonitor().worked(1);
312 parsePluginListDocument(site, list);
313 if (canceled) return;
314 getProgressMonitor().worked(1);
315 if (canceled) return;
316 }
317 }
318 // remove old stuff or whole update process is broken
319 for (File file: siteCacheFiles) {
320 Utils.deleteFile(file);
321 }
322 }
323
324 /**
325 * Replies true if the task was canceled
326 * @return <code>true</code> if the task was stopped by the user
327 */
328 public boolean isCanceled() {
329 return canceled;
330 }
331
332 /**
333 * Replies the list of plugins described in the downloaded plugin lists
334 *
335 * @return the list of plugins
336 * @since 5601
337 */
338 public List<PluginInformation> getAvailablePlugins() {
339 return availablePlugins;
340 }
341}
Note: See TracBrowser for help on using the repository browser.