Ticket #8509: async_v1.patch

File async_v1.patch, 12.8 KB (added by udit, 8 years ago)

Patch for Asynchronous uploads in JOSM. This is a version_1

  • src/org/openstreetmap/josm/actions/UploadAction.java

     
    2424import org.openstreetmap.josm.data.osm.Changeset;
    2525import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    2626import org.openstreetmap.josm.gui.MainApplication;
     27import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
    2728import org.openstreetmap.josm.gui.io.UploadDialog;
    28 import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
    2929import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    3030import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    3131import org.openstreetmap.josm.gui.util.GuiHelper;
     
    258258        }
    259259
    260260        MainApplication.worker.execute(
    261                 new UploadPrimitivesTask(
     261                AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
    262262                        UploadDialog.getUploadDialog().getUploadStrategySpecification(),
    263263                        layer,
    264264                        apiData,
    265                         cs
    266                 )
    267         );
     265                        cs)
     266                        .get());
    268267    }
    269268
    270269    @Override
  • src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import org.openstreetmap.josm.data.APIDataSet;
     5import org.openstreetmap.josm.data.osm.Changeset;
     6import org.openstreetmap.josm.data.osm.DataSet;
     7import org.openstreetmap.josm.data.osm.OsmPrimitive;
     8import org.openstreetmap.josm.gui.MainApplication;
     9import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     10import org.openstreetmap.josm.gui.progress.ProgressTaskId;
     11import org.openstreetmap.josm.gui.util.GuiHelper;
     12import org.openstreetmap.josm.io.UploadStrategySpecification;
     13import org.openstreetmap.josm.tools.I18n;
     14
     15import javax.swing.*;
     16import java.awt.*;
     17import java.util.List;
     18import java.util.Optional;
     19
     20/**
     21 * Allows to upload the primitives in background
     22 */
     23public class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask {
     24
     25    /**
     26     * Static instance
     27     */
     28    private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask = null;
     29    private static final boolean HIDE_UPLOADING_PRIMITIVES = false;
     30    private static final String TRANSIENT_LAYER_NAME = "Transient upload layer";
     31
     32    /**
     33     * Member fields
     34     */
     35    private final ProgressTaskId taskId;
     36    private final List<OsmPrimitive> osmPrimitiveList;
     37    private final OsmDataLayer uploadDataLayer;
     38
     39    /**
     40     * Private constructor to restrict creating more Asynchronous upload tasks
     41     *
     42     * @param uploadStrategySpecification
     43     * @param osmDataLayer
     44     * @param apiDataSet
     45     * @param changeset
     46     */
     47    private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification, OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) {
     48        super(uploadStrategySpecification,
     49                osmDataLayer,
     50                apiDataSet,
     51                changeset);
     52
     53        uploadDataLayer = osmDataLayer;
     54
     55        // Get the primitives that are added/deleted/edited
     56        osmPrimitiveList = apiDataSet.getPrimitives();
     57
     58        // Create a ProgressTaskId for background upload
     59        taskId = new ProgressTaskId("core", "async-upload");
     60    }
     61
     62    /**
     63     * Creates a transient OsmDataLayer with DataSet containing the primitives to be added/updated/deleted
     64     *
     65     * @return
     66     */
     67    private static OsmDataLayer createTransientOsmDataLayer(String name) {
     68        DataSet dataSet = new DataSet();
     69        return new OsmDataLayer(dataSet, name, null);
     70    }
     71
     72    /**
     73     * Disables the list of primitives and their referrers
     74     * @param osmPrimitiveList
     75     */
     76    private static void disablePrimitives(List <OsmPrimitive> osmPrimitiveList) {
     77        for (OsmPrimitive p : osmPrimitiveList) {
     78            if(!p.isDeleted()) {
     79                p.setDisabledState(HIDE_UPLOADING_PRIMITIVES);
     80                for (OsmPrimitive ref : p.getReferrers()) {
     81                    ref.setDisabledState(HIDE_UPLOADING_PRIMITIVES);
     82                }
     83            }
     84        }
     85    }
     86
     87    /**
     88     * Enables the list of primitives provided
     89     * @param osmPrimitiveList
     90     */
     91    private static void enablePrimitives(List <OsmPrimitive> osmPrimitiveList) {
     92        for (OsmPrimitive p : osmPrimitiveList) {
     93
     94            // Only enable the primitives that are still present
     95            if (!p.isDeleted()) {
     96                p.unsetDisabledState();
     97
     98                // Enable the referrers of all primitives
     99                for (OsmPrimitive ref : p.getReferrers()) {
     100                    if (!ref.isDeleted())
     101                        ref.unsetDisabledState();
     102                }
     103            }
     104        }
     105    }
     106
     107    /**
     108     * Creates an instance of AsynchronousUploadPrimitiveTask
     109     *
     110     * @param uploadStrategySpecification
     111     * @param dataLayer
     112     * @param apiDataSet
     113     * @param changeset
     114     * @return Optional<AsynchronousUploadPrimitivesTask>
     115     */
     116    public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask
     117            (UploadStrategySpecification uploadStrategySpecification,
     118             OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) {
     119        synchronized (AsynchronousUploadPrimitivesTask.class) {
     120            if (asynchronousUploadPrimitivesTask != null) {
     121                if (!GraphicsEnvironment.isHeadless()) {
     122                    GuiHelper.runInEDTAndWait(() ->
     123                            JOptionPane.showMessageDialog(MainApplication.parent,
     124                                    I18n.tr("A background upload is already in progress. Kindly wait for it to finish before uploading new changes")));
     125                }
     126                return Optional.empty();
     127            } else {
     128                // Get a transient upload layer
     129                OsmDataLayer transientLayer = createTransientOsmDataLayer(TRANSIENT_LAYER_NAME);
     130                MainApplication.getLayerManager().addLayer(transientLayer);
     131                MainApplication.getLayerManager().setActiveLayer(transientLayer);
     132
     133                // Create an asynchronous upload task with the transient layer
     134                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
     135                        uploadStrategySpecification,
     136                        dataLayer,
     137                        apiDataSet,
     138                        changeset);
     139                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     140            }
     141        }
     142    }
     143
     144    /**
     145     * Get the current upload task
     146     * @return Optional<AsynchronousUploadPrimitivesTask>
     147     */
     148    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask () {
     149        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     150    }
     151
     152    /**
     153     * Lock the dataset
     154     */
     155    protected void lockDataSet () {
     156        uploadDataLayer.data.beginUpdate();
     157    }
     158
     159    /**
     160     * Unlock the upload task dataset
     161     */
     162    protected void unlockDataSet () {
     163        uploadDataLayer.data.endUpdate();
     164    }
     165
     166    /**
     167     * Merge the transient upload layer with the current edit layer
     168     */
     169    private void mergeTransientLayer () {
     170        OsmDataLayer currentEditLayer = MainApplication.getLayerManager().getEditLayer();
     171        currentEditLayer.mergeFrom(uploadDataLayer);
     172        MainApplication.getLayerManager().removeLayer(uploadDataLayer);
     173        currentEditLayer.rename(uploadDataLayer.getName());
     174    }
     175
     176    @Override
     177    protected void cleanupAfterUpload () {
     178        // The cleanup happens on EDT. Hence unlocking the dataset
     179        unlockDataSet();
     180        super.cleanupAfterUpload();
     181    }
     182
     183    @Override
     184    public ProgressTaskId canRunInBackground() {
     185        return taskId;
     186    }
     187
     188    @Override
     189    protected void realRun() {
     190        // Lock the dataset for any changes from other threads
     191        lockDataSet();
     192        super.realRun();
     193    }
     194
     195    @Override
     196    protected void cancel() {
     197        super.cancel();
     198        asynchronousUploadPrimitivesTask = null;
     199    }
     200
     201    @Override
     202    protected void finish() {
     203        try {
     204            // Note: Always merge back the layers.
     205            // Even in cases with conflicts/failures the users changes should not be discarded.
     206            mergeTransientLayer();
     207
     208            super.finish();
     209        } finally {
     210            asynchronousUploadPrimitivesTask = null;
     211        }
     212    }
     213}
     214 No newline at end of file
  • test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     5import org.junit.*;
     6import org.openstreetmap.josm.data.APIDataSet;
     7import org.openstreetmap.josm.data.coor.LatLon;
     8import org.openstreetmap.josm.data.osm.*;
     9import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     10import org.openstreetmap.josm.io.UploadStrategySpecification;
     11import org.openstreetmap.josm.testutils.JOSMTestRules;
     12
     13import java.util.*;
     14
     15public class AsynchronousUploadPrimitivesTaskTest {
     16
     17    private UploadStrategySpecification strategy;
     18    private OsmDataLayer layer;
     19    private APIDataSet toUpload;
     20    private Changeset changeset;
     21    private AsynchronousUploadPrimitivesTask uploadPrimitivesTask;
     22
     23    /**
     24     * Setup tests
     25     */
     26    @Rule
     27    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     28    public JOSMTestRules test = new JOSMTestRules();
     29
     30    @Before
     31    public void bootStrap() {
     32        DataSet dataSet = new DataSet();
     33        Node node1 = new Node();
     34        Node node2 = new Node();
     35        node1.setCoor(new LatLon(0, 0));
     36        node2.setCoor(new LatLon(30, 30));
     37        Way way = new Way();
     38        way.addNode(node1);
     39        way.addNode(node2);
     40        dataSet.addPrimitive(node1);
     41        dataSet.addPrimitive(node2);
     42        dataSet.addPrimitive(way);
     43
     44        toUpload = new APIDataSet(dataSet);
     45        layer = new OsmDataLayer(dataSet, "uploadTest", null);
     46        strategy = new UploadStrategySpecification();
     47        changeset = new Changeset();
     48        uploadPrimitivesTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset).get();
     49    }
     50
     51    @After
     52    public void tearDown () {
     53        toUpload = null;
     54        layer = null;
     55        strategy = null;
     56        changeset = null;
     57        uploadPrimitivesTask = null;
     58    }
     59
     60    @Test
     61    public void testSingleUploadInstance () {
     62        Optional<AsynchronousUploadPrimitivesTask> task = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset);
     63        Assert.assertFalse(task.isPresent());
     64    }
     65
     66    @Test
     67    public void testPrimitiveCannotBeAdded () throws InterruptedException {
     68        Node newNode = new Node();
     69        newNode.setCoor(new LatLon(1,1));
     70        uploadPrimitivesTask.lockDataSet();
     71        Thread otherThread = new Thread(() -> layer.data.addPrimitive(newNode));
     72
     73        otherThread.start();
     74        otherThread.join(1000);
     75        Assert.assertFalse(layer.data.containsNode(newNode));
     76        uploadPrimitivesTask.unlockDataSet();
     77    }
     78
     79    @Test
     80    public void testPrimitiveCannotBeEdited () throws InterruptedException {
     81        Way way = layer.data.getWays().iterator().next();
     82        uploadPrimitivesTask.lockDataSet();
     83        Thread otherThread = new Thread(() -> way.put("test-key", "test-value"));
     84
     85        otherThread.start();
     86        otherThread.join(1000);
     87        Assert.assertNull(way.get("test-key"));
     88        uploadPrimitivesTask.unlockDataSet();
     89    }
     90
     91    @Test
     92    public void testPrimitiveCannotBeDeleted () throws InterruptedException {
     93        Node node1 = layer.data.getNodes().iterator().next();
     94        uploadPrimitivesTask.lockDataSet();
     95        Thread otherThread = new Thread(() -> layer.data.removePrimitive(node1.getPrimitiveId()));
     96
     97        otherThread.start();
     98        otherThread.join(1000);
     99        Assert.assertTrue(layer.data.containsNode(node1));
     100        uploadPrimitivesTask.unlockDataSet();
     101    }
     102}