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

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

code cleanup

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