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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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