Ticket #8509: async_v7.patch
File async_v7.patch, 18.7 KB (added by , 7 years ago) |
---|
-
src/org/openstreetmap/josm/actions/UploadAction.java
9 9 import java.util.LinkedList; 10 10 import java.util.List; 11 11 import java.util.Map; 12 import java.util.Optional; 12 13 13 14 import javax.swing.JOptionPane; 14 15 … … 24 25 import org.openstreetmap.josm.data.osm.Changeset; 25 26 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 26 27 import org.openstreetmap.josm.gui.MainApplication; 28 import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask; 27 29 import org.openstreetmap.josm.gui.io.UploadDialog; 28 30 import org.openstreetmap.josm.gui.io.UploadPrimitivesTask; 29 31 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; … … 57 59 private static final List<UploadHook> UPLOAD_HOOKS = new LinkedList<>(); 58 60 private static final List<UploadHook> LATE_UPLOAD_HOOKS = new LinkedList<>(); 59 61 62 private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload"; 63 60 64 static { 61 65 /** 62 66 * Calls validator before upload. … … 259 263 hook.modifyChangesetTags(changesetTags); 260 264 } 261 265 262 MainApplication.worker.execute( 263 new UploadPrimitivesTask( 264 UploadDialog.getUploadDialog().getUploadStrategySpecification(), 265 layer, 266 apiData, 267 cs 268 ) 269 ); 266 if (Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).isEmpty() || 267 !Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).equalsIgnoreCase("disabled")) { 268 Optional<AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask( 269 UploadDialog.getUploadDialog().getUploadStrategySpecification(), 270 layer, 271 apiData, 272 cs); 273 274 if (asyncUploadTask.isPresent()) { 275 MainApplication.worker.execute(asyncUploadTask.get()); 276 } 277 } else { 278 MainApplication.worker.execute( 279 new UploadPrimitivesTask( 280 UploadDialog.getUploadDialog().getUploadStrategySpecification(), 281 layer, 282 apiData, 283 cs)); 284 } 270 285 } 271 286 272 287 @Override -
src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.io; 3 4 import org.openstreetmap.josm.data.APIDataSet; 5 import org.openstreetmap.josm.data.osm.Changeset; 6 import org.openstreetmap.josm.gui.MainApplication; 7 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 8 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 9 import org.openstreetmap.josm.gui.progress.ProgressTaskId; 10 import org.openstreetmap.josm.gui.util.GuiHelper; 11 import org.openstreetmap.josm.io.UploadStrategySpecification; 12 13 14 import javax.swing.JOptionPane; 15 import java.awt.GraphicsEnvironment; 16 import java.util.Optional; 17 18 import static org.openstreetmap.josm.tools.I18n.tr; 19 20 /** 21 * Task for uploading primitives using background worker threads. The actual upload is delegated to the 22 * {@link UploadPrimitivesTask}. This class is a wrapper over that to make the background upload process safe. There 23 * can only be one instance of this class, hence background uploads are limited to one at a time. This class also 24 * changes the editLayer of {@link org.openstreetmap.josm.gui.layer.MainLayerManager} to null during upload so that 25 * any changes to the uploading layer are prohibited. 26 * 27 * @author udit 28 * @since xxx 29 */ 30 public final class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask { 31 32 /** 33 * Static instance 34 */ 35 private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask = null; 36 37 /** 38 * Member fields 39 */ 40 private final ProgressTaskId taskId; 41 private final OsmDataLayer uploadDataLayer; 42 43 /** 44 * Private constructor to restrict creating more Asynchronous upload tasks 45 * 46 * @param uploadStrategySpecification UploadStrategySpecification for the DataLayer 47 * @param osmDataLayer Datalayer to be uploaded 48 * @param apiDataSet ApiDataSet that contains the primitives to be uploaded 49 * @param changeset Changeset for the datalayer 50 * 51 * @throws IllegalArgumentException if layer is null 52 * @throws IllegalArgumentException if toUpload is null 53 * @throws IllegalArgumentException if strategy is null 54 * @throws IllegalArgumentException if changeset is null 55 */ 56 private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification, 57 OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) { 58 super(uploadStrategySpecification, 59 osmDataLayer, 60 apiDataSet, 61 changeset); 62 63 uploadDataLayer = osmDataLayer; 64 // Create a ProgressTaskId for background upload 65 taskId = new ProgressTaskId("core", "async-upload"); 66 } 67 68 /** 69 * Creates an instance of AsynchronousUploadPrimitiveTask 70 * 71 * @param uploadStrategySpecification UploadStrategySpecification for the DataLayer 72 * @param dataLayer Datalayer to be uploaded 73 * @param apiDataSet ApiDataSet that contains the primitives to be uploaded 74 * @param changeset Changeset for the datalayer 75 * @return Returns an {@literal Optional<AsynchronousUploadPrimitivesTask> } if there is no 76 * background upload in progress. Otherwise returns an {@literal Optional.empty()} 77 * 78 * @throws IllegalArgumentException if layer is null 79 * @throws IllegalArgumentException if toUpload is null 80 * @throws IllegalArgumentException if strategy is null 81 * @throws IllegalArgumentException if changeset is null 82 */ 83 public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask( 84 UploadStrategySpecification uploadStrategySpecification, 85 OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) { 86 synchronized (AsynchronousUploadPrimitivesTask.class) { 87 if (asynchronousUploadPrimitivesTask != null) { 88 if (!GraphicsEnvironment.isHeadless()) { 89 GuiHelper.runInEDTAndWait(() -> 90 JOptionPane.showMessageDialog(MainApplication.parent, 91 tr("A background upload is already in progress. " + 92 "Kindly wait for it to finish before uploading new changes"))); 93 } 94 return Optional.empty(); 95 } else { 96 // Create an asynchronous upload task 97 asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask( 98 uploadStrategySpecification, 99 dataLayer, 100 apiDataSet, 101 changeset); 102 return Optional.ofNullable(asynchronousUploadPrimitivesTask); 103 } 104 } 105 } 106 107 /** 108 * Get the current upload task 109 * @return {@literal Optional<AsynchronousUploadPrimitivesTask> } 110 */ 111 public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask() { 112 return Optional.ofNullable(asynchronousUploadPrimitivesTask); 113 } 114 115 @Override 116 public ProgressTaskId canRunInBackground() { 117 return taskId; 118 } 119 120 @Override 121 protected void realRun() { 122 // Lock the data layer before upload in EDT 123 GuiHelper.runInEDTAndWait(() -> { 124 // Remove the commands from the undo stack 125 MainApplication.undoRedo.clean(uploadDataLayer.data); 126 MainApplication.getLayerManager().prepareLayerForUpload(uploadDataLayer); 127 128 // Repainting the Layer List dialog to update the icon of the active layer 129 LayerListDialog.getInstance().repaint(); 130 }); 131 super.realRun(); 132 } 133 134 @Override 135 protected void cancel() { 136 super.cancel(); 137 asynchronousUploadPrimitivesTask = null; 138 } 139 140 @Override 141 protected void finish() { 142 try { 143 // Unlock the data layer in EDT 144 GuiHelper.runInEDTAndWait(() -> { 145 MainApplication.getLayerManager().processLayerAfterUpload(uploadDataLayer); 146 LayerListDialog.getInstance().repaint(); 147 }); 148 super.finish(); 149 } finally { 150 asynchronousUploadPrimitivesTask = null; 151 } 152 } 153 } -
src/org/openstreetmap/josm/gui/layer/MainLayerManager.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.layer; 3 3 4 import org.openstreetmap.josm.data.osm.DataSet; 5 import org.openstreetmap.josm.gui.MainApplication; 6 import org.openstreetmap.josm.gui.util.GuiHelper; 7 8 import javax.swing.JOptionPane; 4 9 import java.util.ArrayList; 5 10 import java.util.Collection; 6 11 import java.util.List; … … 7 12 import java.util.ListIterator; 8 13 import java.util.concurrent.CopyOnWriteArrayList; 9 14 10 import org.openstreetmap.josm.data.osm.DataSet; 11 import org.openstreetmap.josm.gui.util.GuiHelper; 15 import static org.openstreetmap.josm.tools.I18n.tr; 12 16 13 17 /** 14 18 * This class extends the layer manager by adding an active and an edit layer. … … 205 209 } 206 210 207 211 /** 208 * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed. 212 * Set the active layer iff the layer is not read only. 213 * If the layer is an OsmDataLayer, the edit layer is also changed. 209 214 * @param layer The active layer. 210 215 */ 211 216 public void setActiveLayer(final Layer layer) { 212 217 // we force this on to the EDT Thread to make events fire from there. 213 218 // The synchronization lock needs to be held by the EDT. 214 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer)); 219 if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isReadOnly()) { 220 GuiHelper.runInEDT(() -> 221 JOptionPane.showMessageDialog( 222 MainApplication.parent, 223 tr("Trying to set a read only data layer as edit layer"), 224 tr("Warning"), 225 JOptionPane.WARNING_MESSAGE)); 226 } else { 227 GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer)); 228 } 215 229 } 216 230 217 231 protected synchronized void realSetActiveLayer(final Layer layer) { … … 309 323 * @return the currently active layer (may be null) 310 324 */ 311 325 public synchronized Layer getActiveLayer() { 312 return activeLayer; 326 if (activeLayer instanceof OsmDataLayer) { 327 if (!((OsmDataLayer) activeLayer).isReadOnly()) { 328 return activeLayer; 329 } else { 330 return null; 331 } 332 } else { 333 return activeLayer; 334 } 313 335 } 314 336 315 337 /** 316 * Replies the current edit layer, if any338 * Replies the current edit layer, if present and not readOnly 317 339 * 318 340 * @return the current edit layer. May be null. 319 341 */ 320 342 public synchronized OsmDataLayer getEditLayer() { 321 return editLayer; 343 if (editLayer != null && !editLayer.isReadOnly()) 344 return editLayer; 345 else 346 return null; 322 347 } 323 348 324 349 /** … … 378 403 activeLayerChangeListeners.clear(); 379 404 layerAvailabilityListeners.clear(); 380 405 } 406 407 /** 408 * Prepares an OsmDataLayer for upload. The layer to be uploaded is locked and 409 * if the layer to be uploaded is the current editLayer then editLayer is reset 410 * to null for disallowing any changes to the layer. An ActiveLayerChangeEvent 411 * is fired to notify the listeners 412 * 413 * @param layer The OsmDataLayer to be uploaded 414 */ 415 public void prepareLayerForUpload(OsmDataLayer layer) { 416 417 GuiHelper.assertCallFromEdt(); 418 layer.setReadOnly(); 419 420 // Reset only the edit layer as empty 421 if (editLayer == layer) { 422 ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer); 423 editLayer = null; 424 fireActiveLayerChange(activeLayerChangeEvent); 425 } 426 } 427 428 /** 429 * Post upload processing of the OsmDataLayer. 430 * If the current edit layer is empty this function sets the layer uploaded as the 431 * current editLayer. An ActiveLayerChangeEvent is fired to notify the listeners 432 * 433 * @param layer The OsmDataLayer uploaded 434 */ 435 public void processLayerAfterUpload(OsmDataLayer layer) { 436 GuiHelper.assertCallFromEdt(); 437 layer.unsetReadOnly(); 438 439 // Set the layer as edit layer 440 // iff the edit layer is empty. 441 if (editLayer == null) { 442 ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer); 443 editLayer = layer; 444 fireActiveLayerChange(layerChangeEvent); 445 } 446 } 381 447 } -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
31 31 import java.util.Map; 32 32 import java.util.Set; 33 33 import java.util.concurrent.CopyOnWriteArrayList; 34 import java.util.concurrent.atomic.AtomicBoolean; 34 35 import java.util.concurrent.atomic.AtomicInteger; 35 36 import java.util.regex.Pattern; 36 37 … … 129 130 130 131 private boolean requiresSaveToFile; 131 132 private boolean requiresUploadToServer; 133 /** Flag used to know if the layer should not be editable */ 134 private final AtomicBoolean isReadOnly = new AtomicBoolean(false); 132 135 133 136 /** 134 137 * List of validation errors in this layer. … … 420 423 if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) { 421 424 base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)); 422 425 } 426 427 if (isReadOnly()) { 428 // If the layer is read only then change the default icon to a clock 429 base = new ImageProvider("clock").setMaxSize(ImageSizes.LAYER); 430 } 423 431 return base.get(); 424 432 } 425 433 … … 1142 1150 } 1143 1151 super.setName(name); 1144 1152 } 1153 1154 /** 1155 * Sets the isReadOnly flag for the OsmDataLayer as true 1156 */ 1157 public void setReadOnly() { 1158 if (!isReadOnly.compareAndSet(false, true)) { 1159 Logging.warn("Trying to set readOnly flag on a readOnly layer ", this.getName()); 1160 } 1161 } 1162 1163 /** 1164 * Sets the isReadOnly flag for the OsmDataLayer as false 1165 */ 1166 public void unsetReadOnly() { 1167 if (!isReadOnly.compareAndSet(true, false)) { 1168 Logging.warn("Trying to unset readOnly flag on a non-readOnly layer ", this.getName()); 1169 } 1170 } 1171 1172 /** 1173 * Returns the value of the isReadOnly flag for the OsmDataLayer 1174 * @return isReadOnly 1175 */ 1176 public boolean isReadOnly() { 1177 return isReadOnly.get(); 1178 } 1145 1179 } -
test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.io; 3 4 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 5 import org.junit.Rule; 6 import org.junit.Before; 7 import org.junit.After; 8 import org.junit.Test; 9 import org.junit.Assert; 10 import org.openstreetmap.josm.data.APIDataSet; 11 import org.openstreetmap.josm.data.coor.LatLon; 12 import org.openstreetmap.josm.data.osm.Changeset; 13 import org.openstreetmap.josm.data.osm.DataSet; 14 import org.openstreetmap.josm.data.osm.Node; 15 import org.openstreetmap.josm.data.osm.Way; 16 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 17 import org.openstreetmap.josm.io.UploadStrategySpecification; 18 import org.openstreetmap.josm.testutils.JOSMTestRules; 19 20 import java.util.Optional; 21 22 public class AsynchronousUploadPrimitivesTaskTest { 23 24 private UploadStrategySpecification strategy; 25 private OsmDataLayer layer; 26 private APIDataSet toUpload; 27 private Changeset changeset; 28 private AsynchronousUploadPrimitivesTask uploadPrimitivesTask; 29 30 /** 31 * Setup tests 32 */ 33 @Rule 34 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 35 public JOSMTestRules test = new JOSMTestRules(); 36 37 @Before 38 public void bootStrap() { 39 DataSet dataSet = new DataSet(); 40 Node node1 = new Node(); 41 Node node2 = new Node(); 42 node1.setCoor(new LatLon(0, 0)); 43 node2.setCoor(new LatLon(30, 30)); 44 Way way = new Way(); 45 way.addNode(node1); 46 way.addNode(node2); 47 dataSet.addPrimitive(node1); 48 dataSet.addPrimitive(node2); 49 dataSet.addPrimitive(way); 50 51 toUpload = new APIDataSet(dataSet); 52 layer = new OsmDataLayer(dataSet, "uploadTest", null); 53 strategy = new UploadStrategySpecification(); 54 changeset = new Changeset(); 55 uploadPrimitivesTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset).get(); 56 } 57 58 @After 59 public void tearDown() { 60 toUpload = null; 61 layer = null; 62 strategy = null; 63 changeset = null; 64 uploadPrimitivesTask = null; 65 } 66 67 @Test 68 public void testSingleUploadInstance() { 69 Optional<AsynchronousUploadPrimitivesTask> task = AsynchronousUploadPrimitivesTask. 70 createAsynchronousUploadTask(strategy, layer, toUpload, changeset); 71 Assert.assertNotNull(uploadPrimitivesTask); 72 Assert.assertFalse(task.isPresent()); 73 } 74 }