source: josm/trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java@ 13453

Last change on this file since 13453 was 13453, checked in by Don-vip, 6 years ago

fix #8039, fix #10456: final fixes for the read-only/locked layers:

  • rename "read-only" to "locked" (in XML and Java classes/interfaces)
  • add a new download policy (true/never) to allow private layers forbidding only to download data, but allowing everything else

This leads to:

  • normal layers: download allowed, modifications allowed, upload allowed
  • private layers: download allowed or not (download=true/never), modifications allowed, upload allowed or not (upload=true/discouraged/never)
  • locked layers: nothing allowed, the data cannot be modified in any way
  • Property svn:eol-style set to native
File size: 17.9 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[153]2package org.openstreetmap.josm.actions.downloadtasks;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
[5691]7import java.net.URL;
[6524]8import java.util.ArrayList;
[12679]9import java.util.Arrays;
[1869]10import java.util.Collection;
[11836]11import java.util.Collections;
[11735]12import java.util.HashSet;
[11553]13import java.util.Optional;
[11735]14import java.util.Set;
[1465]15import java.util.concurrent.Future;
[5024]16import java.util.regex.Matcher;
17import java.util.regex.Pattern;
[13434]18import java.util.stream.Stream;
[153]19
[1465]20import org.openstreetmap.josm.data.Bounds;
[7575]21import org.openstreetmap.josm.data.DataSource;
[7816]22import org.openstreetmap.josm.data.ProjectionBounds;
[11774]23import org.openstreetmap.josm.data.ViewportData;
[3066]24import org.openstreetmap.josm.data.coor.LatLon;
[153]25import org.openstreetmap.josm.data.osm.DataSet;
[11735]26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Relation;
28import org.openstreetmap.josm.data.osm.Way;
[2477]29import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
[12630]30import org.openstreetmap.josm.gui.MainApplication;
31import org.openstreetmap.josm.gui.MapFrame;
[153]32import org.openstreetmap.josm.gui.PleaseWaitRunnable;
[11735]33import org.openstreetmap.josm.gui.io.UpdatePrimitivesTask;
[153]34import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[5402]35import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
[1811]36import org.openstreetmap.josm.gui.progress.ProgressMonitor;
[153]37import org.openstreetmap.josm.io.BoundingBoxDownloader;
[1146]38import org.openstreetmap.josm.io.OsmServerLocationReader;
[12679]39import org.openstreetmap.josm.io.OsmServerLocationReader.OsmUrlPattern;
[1146]40import org.openstreetmap.josm.io.OsmServerReader;
[4310]41import org.openstreetmap.josm.io.OsmTransferCanceledException;
[1670]42import org.openstreetmap.josm.io.OsmTransferException;
[12620]43import org.openstreetmap.josm.tools.Logging;
[6524]44import org.openstreetmap.josm.tools.Utils;
[5782]45import org.xml.sax.SAXException;
[153]46
47/**
48 * Open the download dialog and download the data.
49 * Run in the worker thread.
50 */
[8908]51public class DownloadOsmTask extends AbstractDownloadTask<DataSet> {
[6069]52
[4523]53 protected Bounds currentBounds;
54 protected DownloadTask downloadTask;
[6069]55
[8840]56 protected String newLayerName;
[6069]57
[8927]58 /** This allows subclasses to ignore this warning */
59 protected boolean warnAboutEmptyArea = true;
60
[6031]61 @Override
62 public String[] getPatterns() {
63 if (this.getClass() == DownloadOsmTask.class) {
[12679]64 return Arrays.stream(OsmUrlPattern.values()).map(OsmUrlPattern::pattern).toArray(String[]::new);
[6031]65 } else {
66 return super.getPatterns();
67 }
68 }
[1082]69
[6031]70 @Override
71 public String getTitle() {
72 if (this.getClass() == DownloadOsmTask.class) {
73 return tr("Download OSM");
74 } else {
75 return super.getTitle();
76 }
77 }
78
[5097]79 @Override
[2328]80 public Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
[5097]81 return download(new BoundingBoxDownloader(downloadArea), newLayer, downloadArea, progressMonitor);
82 }
[2414]83
[5402]84 /**
85 * Asynchronously launches the download task for a given bounding box.
86 *
87 * Set <code>progressMonitor</code> to null, if the task should create, open, and close a progress monitor.
88 * Set progressMonitor to {@link NullProgressMonitor#INSTANCE} if progress information is to
89 * be discarded.
90 *
91 * You can wait for the asynchronous download task to finish by synchronizing on the returned
92 * {@link Future}, but make sure not to freeze up JOSM. Example:
93 * <pre>
[6830]94 * Future&lt;?&gt; future = task.download(...);
[5402]95 * // DON'T run this on the Swing EDT or JOSM will freeze
96 * future.get(); // waits for the dowload task to complete
97 * </pre>
98 *
99 * The following example uses a pattern which is better suited if a task is launched from
100 * the Swing EDT:
101 * <pre>
[6830]102 * final Future&lt;?&gt; future = task.download(...);
[5402]103 * Runnable runAfterTask = new Runnable() {
104 * public void run() {
105 * // this is not strictly necessary because of the type of executor service
106 * // Main.worker is initialized with, but it doesn't harm either
107 * //
108 * future.get(); // wait for the download task to complete
109 * doSomethingAfterTheTaskCompleted();
110 * }
111 * }
[12634]112 * MainApplication.worker.submit(runAfterTask);
[5402]113 * </pre>
114 * @param reader the reader used to parse OSM data (see {@link OsmServerReader#parseOsm})
115 * @param newLayer true, if the data is to be downloaded into a new layer. If false, the task
116 * selects one of the existing layers as download layer, preferably the active layer.
117 * @param downloadArea the area to download
118 * @param progressMonitor the progressMonitor
119 * @return the future representing the asynchronous task
120 */
[5097]121 public Future<?> download(OsmServerReader reader, boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
[11658]122 return download(new DownloadTask(newLayer, reader, progressMonitor, zoomAfterDownload), downloadArea);
[5097]123 }
124
125 protected Future<?> download(DownloadTask downloadTask, Bounds downloadArea) {
126 this.downloadTask = downloadTask;
127 this.currentBounds = new Bounds(downloadArea);
[2322]128 // We need submit instead of execute so we can wait for it to finish and get the error
129 // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
[12634]130 return MainApplication.worker.submit(downloadTask);
[2322]131 }
132
[6588]133 /**
134 * This allows subclasses to perform operations on the URL before {@link #loadUrl} is performed.
[8927]135 * @param url the original URL
136 * @return the modified URL
[6588]137 */
138 protected String modifyUrlBeforeLoad(String url) {
[5782]139 return url;
140 }
[6069]141
[2322]142 /**
143 * Loads a given URL from the OSM Server
[8927]144 * @param newLayer True if the data should be saved to a new layer
[5369]145 * @param url The URL as String
[2322]146 */
[6031]147 @Override
[8927]148 public Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
149 String newUrl = modifyUrlBeforeLoad(url);
150 downloadTask = new DownloadTask(newLayer,
151 new OsmServerLocationReader(newUrl),
[2322]152 progressMonitor);
[3064]153 currentBounds = null;
[5024]154 // Extract .osm filename from URL to set the new layer name
[8927]155 extractOsmFilename("https?://.*/(.*\\.osm)", newUrl);
[12634]156 return MainApplication.worker.submit(downloadTask);
[2322]157 }
[6069]158
[5345]159 protected final void extractOsmFilename(String pattern, String url) {
160 Matcher matcher = Pattern.compile(pattern).matcher(url);
161 newLayerName = matcher.matches() ? matcher.group(1) : null;
162 }
[6031]163
[4521]164 @Override
[2322]165 public void cancel() {
166 if (downloadTask != null) {
167 downloadTask.cancel();
168 }
169 }
170
[7749]171 @Override
172 public boolean isSafeForRemotecontrolRequests() {
173 return true;
174 }
175
[11774]176 @Override
177 public ProjectionBounds getDownloadProjectionBounds() {
178 return downloadTask != null ? downloadTask.computeBbox(currentBounds) : null;
179 }
180
[7636]181 /**
182 * Superclass of internal download task.
[7637]183 * @since 7636
[7636]184 */
[8130]185 public abstract static class AbstractInternalTask extends PleaseWaitRunnable {
[7636]186
187 protected final boolean newLayer;
[8927]188 protected final boolean zoomAfterDownload;
[4523]189 protected DataSet dataSet;
[1647]190
[7636]191 /**
192 * Constructs a new {@code AbstractInternalTask}.
193 * @param newLayer if {@code true}, force download to a new layer
194 * @param title message for the user
195 * @param ignoreException If true, exception will be propagated to calling code. If false then
196 * exception will be thrown directly in EDT. When this runnable is executed using executor framework
197 * then use false unless you read result of task (because exception will get lost if you don't)
[8927]198 * @param zoomAfterDownload If true, the map view will zoom to download area after download
[7636]199 */
[8927]200 public AbstractInternalTask(boolean newLayer, String title, boolean ignoreException, boolean zoomAfterDownload) {
[7636]201 super(title, ignoreException);
[1169]202 this.newLayer = newLayer;
[8927]203 this.zoomAfterDownload = zoomAfterDownload;
[1169]204 }
[6069]205
[7636]206 /**
207 * Constructs a new {@code AbstractInternalTask}.
208 * @param newLayer if {@code true}, force download to a new layer
209 * @param title message for the user
210 * @param progressMonitor progress monitor
211 * @param ignoreException If true, exception will be propagated to calling code. If false then
212 * exception will be thrown directly in EDT. When this runnable is executed using executor framework
213 * then use false unless you read result of task (because exception will get lost if you don't)
[8927]214 * @param zoomAfterDownload If true, the map view will zoom to download area after download
[7636]215 */
[8927]216 public AbstractInternalTask(boolean newLayer, String title, ProgressMonitor progressMonitor, boolean ignoreException,
217 boolean zoomAfterDownload) {
[7636]218 super(title, progressMonitor, ignoreException);
219 this.newLayer = newLayer;
[8927]220 this.zoomAfterDownload = zoomAfterDownload;
[4530]221 }
[153]222
[1810]223 protected OsmDataLayer getEditLayer() {
[12630]224 if (!MainApplication.isDisplayingMapView()) return null;
[12636]225 return MainApplication.getLayerManager().getEditLayer();
[1810]226 }
227
[13434]228 /**
229 * Returns the number of modifiable data layers
230 * @return number of modifiable data layers
231 * @deprecated Use {@link #getNumModifiableDataLayers}
232 */
233 @Deprecated
[1869]234 protected int getNumDataLayers() {
[13434]235 return (int) getNumModifiableDataLayers();
[1869]236 }
237
[13434]238 private static Stream<OsmDataLayer> getModifiableDataLayers() {
239 return MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class)
[13453]240 .stream().filter(OsmDataLayer::isDownloadable);
[1869]241 }
[6069]242
[13434]243 /**
244 * Returns the number of modifiable data layers
245 * @return number of modifiable data layers
246 * @since 13434
247 */
248 protected long getNumModifiableDataLayers() {
249 return getModifiableDataLayers().count();
250 }
251
252 /**
253 * Returns the first modifiable data layer
254 * @return the first modifiable data layer
255 * @since 13434
256 */
257 protected OsmDataLayer getFirstModifiableDataLayer() {
258 return getModifiableDataLayers().findFirst().orElse(null);
259 }
260
[5024]261 protected OsmDataLayer createNewLayer(String layerName) {
262 if (layerName == null || layerName.isEmpty()) {
263 layerName = OsmDataLayer.createNewName();
264 }
265 return new OsmDataLayer(dataSet, layerName, null);
266 }
[6069]267
[4523]268 protected OsmDataLayer createNewLayer() {
[5024]269 return createNewLayer(null);
[4523]270 }
[1869]271
[7816]272 protected ProjectionBounds computeBbox(Bounds bounds) {
[7636]273 BoundingXYVisitor v = new BoundingXYVisitor();
274 if (bounds != null) {
275 v.visit(bounds);
276 } else {
277 v.computeBoundingBox(dataSet.getNodes());
[1082]278 }
[7816]279 return v.getBounds();
280 }
281
[10409]282 protected OsmDataLayer addNewLayerIfRequired(String newLayerName) {
[13434]283 long numDataLayers = getNumModifiableDataLayers();
[1869]284 if (newLayer || numDataLayers == 0 || (numDataLayers > 1 && getEditLayer() == null)) {
285 // the user explicitly wants a new layer, we don't have any layer at all
286 // or it is not clear which layer to merge to
[7636]287 final OsmDataLayer layer = createNewLayer(newLayerName);
[12636]288 MainApplication.getLayerManager().addLayer(layer, zoomAfterDownload);
[7636]289 return layer;
290 }
291 return null;
292 }
293
294 protected void loadData(String newLayerName, Bounds bounds) {
[10409]295 OsmDataLayer layer = addNewLayerIfRequired(newLayerName);
[7636]296 if (layer == null) {
[13434]297 layer = Optional.ofNullable(getEditLayer()).orElseGet(this::getFirstModifiableDataLayer);
[11735]298 Collection<OsmPrimitive> primitivesToUpdate = searchPrimitivesToUpdate(bounds, layer.data);
[7636]299 layer.mergeFrom(dataSet);
[12630]300 MapFrame map = MainApplication.getMap();
301 if (map != null && zoomAfterDownload && bounds != null) {
302 map.mapView.zoomTo(new ViewportData(computeBbox(bounds)));
[8927]303 }
[11735]304 if (!primitivesToUpdate.isEmpty()) {
[12634]305 MainApplication.worker.submit(new UpdatePrimitivesTask(layer, primitivesToUpdate));
[11735]306 }
[7636]307 layer.onPostDownloadFromServer();
[1670]308 }
[1169]309 }
[11735]310
311 /**
312 * Look for primitives deleted on server (thus absent from downloaded data)
313 * but still present in existing data layer
[11836]314 * @param bounds download bounds
[11735]315 * @param ds existing data set
316 * @return the primitives to update
317 */
[11836]318 private Collection<OsmPrimitive> searchPrimitivesToUpdate(Bounds bounds, DataSet ds) {
319 if (bounds == null)
[11849]320 return Collections.emptySet();
[11735]321 Collection<OsmPrimitive> col = new ArrayList<>();
[11836]322 ds.searchNodes(bounds.toBBox()).stream().filter(n -> !n.isNew() && !dataSet.containsNode(n)).forEachOrdered(col::add);
[11735]323 if (!col.isEmpty()) {
324 Set<Way> ways = new HashSet<>();
325 Set<Relation> rels = new HashSet<>();
326 for (OsmPrimitive n : col) {
327 for (OsmPrimitive ref : n.getReferrers()) {
328 if (ref.isNew()) {
329 continue;
330 } else if (ref instanceof Way) {
331 ways.add((Way) ref);
332 } else if (ref instanceof Relation) {
333 rels.add((Relation) ref);
334 }
335 }
336 }
337 ways.stream().filter(w -> !dataSet.containsWay(w)).forEachOrdered(col::add);
338 rels.stream().filter(r -> !dataSet.containsRelation(r)).forEachOrdered(col::add);
339 }
340 return col;
341 }
[7636]342 }
[6069]343
[7636]344 protected class DownloadTask extends AbstractInternalTask {
345 protected final OsmServerReader reader;
346
[8927]347 /**
348 * Constructs a new {@code DownloadTask}.
349 * @param newLayer if {@code true}, force download to a new layer
350 * @param reader OSM data reader
351 * @param progressMonitor progress monitor
352 */
[7636]353 public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor) {
[8942]354 this(newLayer, reader, progressMonitor, true);
355 }
356
357 /**
358 * Constructs a new {@code DownloadTask}.
359 * @param newLayer if {@code true}, force download to a new layer
360 * @param reader OSM data reader
361 * @param progressMonitor progress monitor
362 * @param zoomAfterDownload If true, the map view will zoom to download area after download
363 * @since 8942
364 */
365 public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor, boolean zoomAfterDownload) {
366 super(newLayer, tr("Downloading data"), progressMonitor, false, zoomAfterDownload);
[7636]367 this.reader = reader;
368 }
369
370 protected DataSet parseDataSet() throws OsmTransferException {
371 return reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
372 }
373
374 @Override
375 public void realRun() throws IOException, SAXException, OsmTransferException {
376 try {
377 if (isCanceled())
378 return;
379 dataSet = parseDataSet();
[10212]380 } catch (OsmTransferException e) {
[7636]381 if (isCanceled()) {
[12620]382 Logging.info(tr("Ignoring exception because download has been canceled. Exception was: {0}", e.toString()));
[7636]383 return;
384 }
385 if (e instanceof OsmTransferCanceledException) {
386 setCanceled(true);
387 return;
[10212]388 } else {
[7636]389 rememberException(e);
390 }
391 DownloadOsmTask.this.setFailed(true);
[4530]392 }
393 }
[175]394
[7636]395 @Override
396 protected void finish() {
397 if (isFailed() || isCanceled())
398 return;
399 if (dataSet == null)
400 return; // user canceled download or error occurred
401 if (dataSet.allPrimitives().isEmpty()) {
[8927]402 if (warnAboutEmptyArea) {
403 rememberErrorMessage(tr("No data found in this area."));
404 }
[8540]405 // need to synthesize a download bounds lest the visual indication of downloaded area doesn't work
[11627]406 dataSet.addDataSource(new DataSource(currentBounds != null ? currentBounds :
[9214]407 new Bounds(LatLon.ZERO), "OpenStreetMap server"));
[7636]408 }
409
410 rememberDownloadedData(dataSet);
411 loadData(newLayerName, currentBounds);
412 }
413
414 @Override
415 protected void cancel() {
[2322]416 setCanceled(true);
[1670]417 if (reader != null) {
[1169]418 reader.cancel();
[1670]419 }
[1169]420 }
421 }
[5691]422
423 @Override
424 public String getConfirmationMessage(URL url) {
425 if (url != null) {
426 String urlString = url.toExternalForm();
[12679]427 if (urlString.matches(OsmUrlPattern.OSM_API_URL.pattern())) {
[5691]428 // TODO: proper i18n after stabilization
[7005]429 Collection<String> items = new ArrayList<>();
[8846]430 items.add(tr("OSM Server URL:") + ' ' + url.getHost());
[6524]431 items.add(tr("Command")+": "+url.getPath());
[5691]432 if (url.getQuery() != null) {
[6524]433 items.add(tr("Request details: {0}", url.getQuery().replaceAll(",\\s*", ", ")));
[5691]434 }
[6524]435 return Utils.joinAsHtmlUnorderedList(items);
[5691]436 }
437 // TODO: other APIs
438 }
439 return null;
440 }
[175]441}
Note: See TracBrowser for help on using the repository browser.