source: josm/trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java

Last change on this file was 17330, checked in by Don-vip, 3 years ago

fix #20131 - remote control: report errors in case of OSM API error (load_and_zoom) or no valid identifier (load_object)

  • Property svn:eol-style set to native
File size: 13.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.downloadtasks;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.EventQueue;
9import java.awt.geom.Area;
10import java.awt.geom.Rectangle2D;
11import java.util.Collection;
12import java.util.LinkedHashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Objects;
16import java.util.Set;
17import java.util.concurrent.CancellationException;
18import java.util.concurrent.ExecutionException;
19import java.util.concurrent.Future;
20import java.util.stream.Collectors;
21
22import javax.swing.JOptionPane;
23
24import org.openstreetmap.josm.actions.UpdateSelectionAction;
25import org.openstreetmap.josm.data.Bounds;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.gui.HelpAwareOptionPane;
29import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
30import org.openstreetmap.josm.gui.MainApplication;
31import org.openstreetmap.josm.gui.Notification;
32import org.openstreetmap.josm.gui.layer.Layer;
33import org.openstreetmap.josm.gui.layer.OsmDataLayer;
34import org.openstreetmap.josm.gui.progress.ProgressMonitor;
35import org.openstreetmap.josm.gui.util.GuiHelper;
36import org.openstreetmap.josm.tools.ImageProvider;
37import org.openstreetmap.josm.tools.Logging;
38import org.openstreetmap.josm.tools.Utils;
39
40/**
41 * This class encapsulates the downloading of several bounding boxes that would otherwise be too
42 * large to download in one go. Error messages will be collected for all downloads and displayed as
43 * a list in the end.
44 * @author xeen
45 * @since 6053
46 */
47public class DownloadTaskList {
48 private final List<DownloadTask> tasks = new LinkedList<>();
49 private final List<Future<?>> taskFutures = new LinkedList<>();
50 private final boolean zoomAfterDownload;
51 private ProgressMonitor progressMonitor;
52
53 /**
54 * Constructs a new {@code DownloadTaskList}. Zooms to each download area.
55 */
56 public DownloadTaskList() {
57 this(true);
58 }
59
60 /**
61 * Constructs a new {@code DownloadTaskList}.
62 * @param zoomAfterDownload whether to zoom to each download area
63 * @since 15205
64 */
65 public DownloadTaskList(boolean zoomAfterDownload) {
66 this.zoomAfterDownload = zoomAfterDownload;
67 }
68
69 private void addDownloadTask(ProgressMonitor progressMonitor, DownloadTask dt, Rectangle2D td, int i, int n) {
70 ProgressMonitor childProgress = progressMonitor.createSubTaskMonitor(1, false);
71 childProgress.setCustomText(tr("Download {0} of {1} ({2} left)", i, n, n - i));
72 dt.setZoomAfterDownload(zoomAfterDownload);
73 Future<?> future = dt.download(new DownloadParams(), new Bounds(td), childProgress);
74 taskFutures.add(future);
75 tasks.add(dt);
76 }
77
78 /**
79 * Downloads a list of areas from the OSM Server
80 * @param newLayer Set to true if all areas should be put into a single new layer
81 * @param rects The List of Rectangle2D to download
82 * @param osmData Set to true if OSM data should be downloaded
83 * @param gpxData Set to true if GPX data should be downloaded
84 * @param progressMonitor The progress monitor
85 * @return The Future representing the asynchronous download task
86 */
87 public Future<?> download(boolean newLayer, List<Rectangle2D> rects, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) {
88 this.progressMonitor = progressMonitor;
89 if (newLayer) {
90 Layer l = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null);
91 MainApplication.getLayerManager().addLayer(l);
92 MainApplication.getLayerManager().setActiveLayer(l);
93 }
94
95 int n = (osmData && gpxData ? 2 : 1)*rects.size();
96 progressMonitor.beginTask(null, n);
97 int i = 0;
98 for (Rectangle2D td : rects) {
99 i++;
100 if (osmData) {
101 addDownloadTask(progressMonitor, new DownloadOsmTask(), td, i, n);
102 }
103 if (gpxData) {
104 addDownloadTask(progressMonitor, new DownloadGpsTask(), td, i, n);
105 }
106 }
107 progressMonitor.addCancelListener(() -> {
108 for (DownloadTask dt : tasks) {
109 dt.cancel();
110 }
111 });
112 return MainApplication.worker.submit(new PostDownloadProcessor(osmData));
113 }
114
115 /**
116 * Downloads a list of areas from the OSM Server
117 * @param newLayer Set to true if all areas should be put into a single new layer
118 * @param areas The Collection of Areas to download
119 * @param osmData Set to true if OSM data should be downloaded
120 * @param gpxData Set to true if GPX data should be downloaded
121 * @param progressMonitor The progress monitor
122 * @return The Future representing the asynchronous download task
123 */
124 public Future<?> download(boolean newLayer, Collection<Area> areas, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) {
125 progressMonitor.beginTask(tr("Updating data"));
126 try {
127 List<Rectangle2D> rects = areas.stream().map(Area::getBounds2D).collect(Collectors.toList());
128 return download(newLayer, rects, osmData, gpxData, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
129 } finally {
130 progressMonitor.finishTask();
131 }
132 }
133
134 /**
135 * Replies the set of ids of all complete, non-new primitives (i.e. those with !primitive.incomplete)
136 * @param ds data set
137 *
138 * @return the set of ids of all complete, non-new primitives
139 */
140 protected Set<OsmPrimitive> getCompletePrimitives(DataSet ds) {
141 return ds.allPrimitives().stream().filter(p -> !p.isIncomplete() && !p.isNew()).collect(Collectors.toSet());
142 }
143
144 /**
145 * Updates the local state of a set of primitives (given by a set of primitive ids) with the
146 * state currently held on the server.
147 *
148 * @param potentiallyDeleted a set of ids to check update from the server
149 */
150 protected void updatePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) {
151 final List<OsmPrimitive> toSelect = potentiallyDeleted.stream().filter(Objects::nonNull).collect(Collectors.toList());
152 EventQueue.invokeLater(() -> UpdateSelectionAction.updatePrimitives(toSelect));
153 }
154
155 /**
156 * Processes a set of primitives (given by a set of their ids) which might be deleted on the
157 * server. First prompts the user whether he wants to check the current state on the server. If
158 * yes, retrieves the current state on the server and checks whether the primitives are indeed
159 * deleted on the server.
160 *
161 * @param potentiallyDeleted a set of primitives (given by their ids)
162 */
163 protected void handlePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) {
164 ButtonSpec[] options = {
165 new ButtonSpec(
166 tr("Check on the server"),
167 new ImageProvider("ok"),
168 tr("Click to check whether objects in your local dataset are deleted on the server"),
169 null /* no specific help topic */),
170 new ButtonSpec(
171 tr("Ignore"),
172 new ImageProvider("cancel"),
173 tr("Click to abort and to resume editing"),
174 null /* no specific help topic */),
175 };
176
177 String message = "<html>" + trn(
178 "There is {0} object in your local dataset which "
179 + "might be deleted on the server.<br>If you later try to delete or "
180 + "update this the server is likely to report a conflict.",
181 "There are {0} objects in your local dataset which "
182 + "might be deleted on the server.<br>If you later try to delete or "
183 + "update them the server is likely to report a conflict.",
184 potentiallyDeleted.size(), potentiallyDeleted.size())
185 + "<br>"
186 + trn("Click <strong>{0}</strong> to check the state of this object on the server.",
187 "Click <strong>{0}</strong> to check the state of these objects on the server.",
188 potentiallyDeleted.size(),
189 options[0].text) + "<br>"
190 + tr("Click <strong>{0}</strong> to ignore." + "</html>", options[1].text);
191
192 int ret = HelpAwareOptionPane.showOptionDialog(
193 MainApplication.getMainFrame(),
194 message,
195 tr("Deleted or moved objects"),
196 JOptionPane.WARNING_MESSAGE,
197 null,
198 options,
199 options[0],
200 ht("/Action/UpdateData#SyncPotentiallyDeletedObjects")
201 );
202 if (ret != 0 /* OK */)
203 return;
204
205 updatePotentiallyDeletedPrimitives(potentiallyDeleted);
206 }
207
208 /**
209 * Replies the set of primitive ids which have been downloaded by this task list
210 *
211 * @return the set of primitive ids which have been downloaded by this task list
212 */
213 public Set<OsmPrimitive> getDownloadedPrimitives() {
214 return tasks.stream()
215 .filter(t -> t instanceof DownloadOsmTask)
216 .map(t -> ((DownloadOsmTask) t).getDownloadedData())
217 .filter(Objects::nonNull)
218 .flatMap(ds -> ds.allPrimitives().stream())
219 .collect(Collectors.toSet());
220 }
221
222 class PostDownloadProcessor implements Runnable {
223
224 private final boolean osmData;
225
226 PostDownloadProcessor(boolean osmData) {
227 this.osmData = osmData;
228 }
229
230 /**
231 * Grabs and displays the error messages after all download threads have finished.
232 */
233 @Override
234 public void run() {
235 progressMonitor.finishTask();
236
237 // wait for all download tasks to finish
238 //
239 for (Future<?> future : taskFutures) {
240 try {
241 future.get();
242 } catch (InterruptedException | ExecutionException | CancellationException e) {
243 Logging.error(e);
244 return;
245 }
246 }
247 Set<String> errors = tasks.stream().flatMap(t -> t.getErrorMessages().stream()).collect(Collectors.toSet());
248 if (!errors.isEmpty()) {
249 GuiHelper.runInEDT(() -> {
250 if (errors.size() == 1 && PostDownloadHandler.isNoDataErrorMessage(errors.iterator().next())) {
251 new Notification(errors.iterator().next()).setIcon(JOptionPane.WARNING_MESSAGE).show();
252 } else {
253 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), "<html>"
254 + tr("The following errors occurred during mass download: {0}",
255 Utils.joinAsHtmlUnorderedList(errors)) + "</html>",
256 tr("Errors during download"), JOptionPane.ERROR_MESSAGE);
257 return;
258 }
259 });
260 }
261
262 // FIXME: this is a hack. We assume that the user canceled the whole download if at
263 // least one task was canceled or if it failed
264 if (Utils.filteredCollection(tasks, AbstractDownloadTask.class).stream()
265 .anyMatch(absTask -> absTask.isCanceled() || absTask.isFailed())) {
266 return;
267 }
268 final DataSet editDataSet = MainApplication.getLayerManager().getEditDataSet();
269 if (editDataSet != null && osmData) {
270 final List<DownloadOsmTask> osmTasks = tasks.stream()
271 .filter(t -> t instanceof DownloadOsmTask).map(t -> (DownloadOsmTask) t)
272 .filter(t -> t.getDownloadedData() != null)
273 .collect(Collectors.toList());
274 final Set<Bounds> tasksBounds = osmTasks.stream()
275 .flatMap(t -> t.getDownloadedData().getDataSourceBounds().stream())
276 .collect(Collectors.toSet());
277 final Set<Bounds> layerBounds = new LinkedHashSet<>(editDataSet.getDataSourceBounds());
278 final Set<OsmPrimitive> myPrimitives = new LinkedHashSet<>();
279 if (layerBounds.equals(tasksBounds)) {
280 // the full edit layer is updated (we have downloaded again all its current bounds)
281 myPrimitives.addAll(getCompletePrimitives(editDataSet));
282 for (DownloadOsmTask task : osmTasks) {
283 // myPrimitives.removeAll(ds.allPrimitives()) will do the same job but much slower
284 task.getDownloadedData().allPrimitives().forEach(myPrimitives::remove);
285 }
286 } else {
287 // partial update, only check what has been downloaded
288 for (DownloadOsmTask task : osmTasks) {
289 myPrimitives.addAll(task.searchPotentiallyDeletedPrimitives(editDataSet));
290 }
291 }
292 if (!myPrimitives.isEmpty()) {
293 GuiHelper.runInEDT(() -> handlePotentiallyDeletedPrimitives(myPrimitives));
294 }
295 }
296 }
297 }
298}
Note: See TracBrowser for help on using the repository browser.