source: josm/trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTaskList.java@ 2328

Last change on this file since 2328 was 2328, checked in by Gubaer, 15 years ago

More cleanup in download logic (less globals, more encapsulation)

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