001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.downloadtasks; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.EventQueue; 009import java.awt.geom.Area; 010import java.awt.geom.Rectangle2D; 011import java.util.ArrayList; 012import java.util.Collection; 013import java.util.HashSet; 014import java.util.LinkedHashSet; 015import java.util.LinkedList; 016import java.util.List; 017import java.util.Set; 018import java.util.concurrent.Future; 019 020import javax.swing.JOptionPane; 021 022import org.openstreetmap.josm.Main; 023import org.openstreetmap.josm.actions.UpdateSelectionAction; 024import org.openstreetmap.josm.data.Bounds; 025import org.openstreetmap.josm.data.osm.DataSet; 026import org.openstreetmap.josm.data.osm.OsmPrimitive; 027import org.openstreetmap.josm.gui.HelpAwareOptionPane; 028import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 029import org.openstreetmap.josm.gui.layer.Layer; 030import org.openstreetmap.josm.gui.layer.OsmDataLayer; 031import org.openstreetmap.josm.gui.progress.ProgressMonitor; 032import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener; 033import org.openstreetmap.josm.gui.util.GuiHelper; 034import org.openstreetmap.josm.tools.ExceptionUtil; 035import org.openstreetmap.josm.tools.ImageProvider; 036 037/** 038 * This class encapsulates the downloading of several bounding boxes that would otherwise be too 039 * large to download in one go. Error messages will be collected for all downloads and displayed as 040 * a list in the end. 041 * @author xeen 042 * @since 6053 043 */ 044public class DownloadTaskList { 045 private List<DownloadTask> tasks = new LinkedList<DownloadTask>(); 046 private List<Future<?>> taskFutures = new LinkedList<Future<?>>(); 047 private ProgressMonitor progressMonitor; 048 049 private void addDownloadTask(DownloadTask dt, Rectangle2D td, int i, int n) { 050 ProgressMonitor childProgress = progressMonitor.createSubTaskMonitor(1, false); 051 childProgress.setCustomText(tr("Download {0} of {1} ({2} left)", i, n, n - i)); 052 Future<?> future = dt.download(false, new Bounds(td), childProgress); 053 taskFutures.add(future); 054 tasks.add(dt); 055 } 056 057 /** 058 * Downloads a list of areas from the OSM Server 059 * @param newLayer Set to true if all areas should be put into a single new layer 060 * @param rects The List of Rectangle2D to download 061 * @param osmData Set to true if OSM data should be downloaded 062 * @param gpxData Set to true if GPX data should be downloaded 063 * @param progressMonitor The progress monitor 064 * @return The Future representing the asynchronous download task 065 */ 066 public Future<?> download(boolean newLayer, List<Rectangle2D> rects, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) { 067 this.progressMonitor = progressMonitor; 068 if (newLayer) { 069 Layer l = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null); 070 Main.main.addLayer(l); 071 Main.map.mapView.setActiveLayer(l); 072 } 073 074 int n = (osmData && gpxData ? 2 : 1)*rects.size(); 075 progressMonitor.beginTask(null, n); 076 int i = 0; 077 for (Rectangle2D td : rects) { 078 i++; 079 if (osmData) { 080 addDownloadTask(new DownloadOsmTask(), td, i, n); 081 } 082 if (gpxData) { 083 addDownloadTask(new DownloadGpsTask(), td, i, n); 084 } 085 } 086 progressMonitor.addCancelListener(new CancelListener() { 087 @Override 088 public void operationCanceled() { 089 for (DownloadTask dt : tasks) { 090 dt.cancel(); 091 } 092 } 093 }); 094 return Main.worker.submit(new PostDownloadProcessor(osmData)); 095 } 096 097 /** 098 * Downloads a list of areas from the OSM Server 099 * @param newLayer Set to true if all areas should be put into a single new layer 100 * @param areas The Collection of Areas to download 101 * @param osmData Set to true if OSM data should be downloaded 102 * @param gpxData Set to true if GPX data should be downloaded 103 * @param progressMonitor The progress monitor 104 * @return The Future representing the asynchronous download task 105 */ 106 public Future<?> download(boolean newLayer, Collection<Area> areas, boolean osmData, boolean gpxData, ProgressMonitor progressMonitor) { 107 progressMonitor.beginTask(tr("Updating data")); 108 try { 109 List<Rectangle2D> rects = new ArrayList<Rectangle2D>(areas.size()); 110 for (Area a : areas) { 111 rects.add(a.getBounds2D()); 112 } 113 114 return download(newLayer, rects, osmData, gpxData, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 115 } finally { 116 progressMonitor.finishTask(); 117 } 118 } 119 120 /** 121 * Replies the set of ids of all complete, non-new primitives (i.e. those with ! 122 * primitive.incomplete) 123 * 124 * @return the set of ids of all complete, non-new primitives 125 */ 126 protected Set<OsmPrimitive> getCompletePrimitives(DataSet ds) { 127 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 128 for (OsmPrimitive primitive : ds.allPrimitives()) { 129 if (!primitive.isIncomplete() && !primitive.isNew()) { 130 ret.add(primitive); 131 } 132 } 133 return ret; 134 } 135 136 /** 137 * Updates the local state of a set of primitives (given by a set of primitive ids) with the 138 * state currently held on the server. 139 * 140 * @param potentiallyDeleted a set of ids to check update from the server 141 */ 142 protected void updatePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) { 143 final List<OsmPrimitive> toSelect = new ArrayList<OsmPrimitive>(); 144 for (OsmPrimitive primitive : potentiallyDeleted) { 145 if (primitive != null) { 146 toSelect.add(primitive); 147 } 148 } 149 EventQueue.invokeLater(new Runnable() { 150 @Override public void run() { 151 UpdateSelectionAction.updatePrimitives(toSelect); 152 } 153 }); 154 } 155 156 /** 157 * Processes a set of primitives (given by a set of their ids) which might be deleted on the 158 * server. First prompts the user whether he wants to check the current state on the server. If 159 * yes, retrieves the current state on the server and checks whether the primitives are indeed 160 * deleted on the server. 161 * 162 * @param potentiallyDeleted a set of primitives (given by their ids) 163 */ 164 protected void handlePotentiallyDeletedPrimitives(Set<OsmPrimitive> potentiallyDeleted) { 165 ButtonSpec[] options = new ButtonSpec[] { 166 new ButtonSpec( 167 tr("Check on the server"), 168 ImageProvider.get("ok"), 169 tr("Click to check whether objects in your local dataset are deleted on the server"), 170 null /* no specific help topic */ 171 ), 172 new ButtonSpec( 173 tr("Ignore"), 174 ImageProvider.get("cancel"), 175 tr("Click to abort and to resume editing"), 176 null /* no specific help topic */ 177 ), 178 }; 179 180 String message = "<html>" + trn( 181 "There is {0} object in your local dataset which " 182 + "might be deleted on the server.<br>If you later try to delete or " 183 + "update this the server is likely to report a conflict.", 184 "There are {0} objects in your local dataset which " 185 + "might be deleted on the server.<br>If you later try to delete or " 186 + "update them the server is likely to report a conflict.", 187 potentiallyDeleted.size(), potentiallyDeleted.size()) 188 + "<br>" 189 + trn("Click <strong>{0}</strong> to check the state of this object on the server.", 190 "Click <strong>{0}</strong> to check the state of these objects on the server.", 191 potentiallyDeleted.size(), 192 options[0].text) + "<br>" 193 + tr("Click <strong>{0}</strong> to ignore." + "</html>", options[1].text); 194 195 int ret = HelpAwareOptionPane.showOptionDialog( 196 Main.parent, 197 message, 198 tr("Deleted or moved objects"), 199 JOptionPane.WARNING_MESSAGE, 200 null, 201 options, 202 options[0], 203 ht("/Action/UpdateData#SyncPotentiallyDeletedObjects") 204 ); 205 if (ret != 0 /* OK */) 206 return; 207 208 updatePotentiallyDeletedPrimitives(potentiallyDeleted); 209 } 210 211 /** 212 * Replies the set of primitive ids which have been downloaded by this task list 213 * 214 * @return the set of primitive ids which have been downloaded by this task list 215 */ 216 public Set<OsmPrimitive> getDownloadedPrimitives() { 217 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 218 for (DownloadTask task : tasks) { 219 if (task instanceof DownloadOsmTask) { 220 DataSet ds = ((DownloadOsmTask) task).getDownloadedData(); 221 if (ds != null) { 222 ret.addAll(ds.allPrimitives()); 223 } 224 } 225 } 226 return ret; 227 } 228 229 class PostDownloadProcessor implements Runnable { 230 231 private final boolean osmData; 232 233 public 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 (Exception e) { 250 e.printStackTrace(); 251 return; 252 } 253 } 254 LinkedHashSet<Object> errors = new LinkedHashSet<Object>(); 255 for (DownloadTask dt : tasks) { 256 errors.addAll(dt.getErrorObjects()); 257 } 258 if (!errors.isEmpty()) { 259 final StringBuilder sb = new StringBuilder(); 260 for (Object error : errors) { 261 if (error instanceof String) { 262 sb.append("<li>").append(error).append("</li>").append("<br>"); 263 } else if (error instanceof Exception) { 264 sb.append("<li>").append(ExceptionUtil.explainException((Exception) error)).append("</li>") 265 .append("<br>"); 266 } 267 } 268 sb.insert(0, "<ul>"); 269 sb.append("</ul>"); 270 271 GuiHelper.runInEDT(new Runnable() { 272 @Override 273 public void run() { 274 JOptionPane.showMessageDialog(Main.parent, "<html>" 275 + tr("The following errors occurred during mass download: {0}", sb.toString()) + "</html>", 276 tr("Errors during download"), JOptionPane.ERROR_MESSAGE); 277 } 278 }); 279 280 return; 281 } 282 283 // FIXME: this is a hack. We assume that the user canceled the whole download if at 284 // least one task was canceled or if it failed 285 // 286 for (DownloadTask task : tasks) { 287 if (task instanceof AbstractDownloadTask) { 288 AbstractDownloadTask absTask = (AbstractDownloadTask) task; 289 if (absTask.isCanceled() || absTask.isFailed()) 290 return; 291 } 292 } 293 final OsmDataLayer editLayer = Main.main.getEditLayer(); 294 if (editLayer != null && osmData) { 295 final Set<OsmPrimitive> myPrimitives = getCompletePrimitives(editLayer.data); 296 for (DownloadTask task : tasks) { 297 if (task instanceof DownloadOsmTask) { 298 DataSet ds = ((DownloadOsmTask) task).getDownloadedData(); 299 if (ds != null) { 300 // myPrimitives.removeAll(ds.allPrimitives()) will do the same job but much slower 301 for (OsmPrimitive primitive: ds.allPrimitives()) { 302 myPrimitives.remove(primitive); 303 } 304 } 305 } 306 } 307 if (!myPrimitives.isEmpty()) { 308 GuiHelper.runInEDT(new Runnable() { 309 @Override public void run() { 310 handlePotentiallyDeletedPrimitives(myPrimitives); 311 } 312 }); 313 } 314 } 315 } 316 } 317}