Ticket #8509: async_v5.patch

File async_v5.patch, 16.0 KB (added by udit, 8 years ago)

Patch for Asynchronous uploads in JOSM. This is a version_5.

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

     
    99import java.util.LinkedList;
    1010import java.util.List;
    1111import java.util.Map;
     12import java.util.Optional;
    1213
    1314import javax.swing.JOptionPane;
    1415
     
    2425import org.openstreetmap.josm.data.osm.Changeset;
    2526import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    2627import org.openstreetmap.josm.gui.MainApplication;
     28import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
    2729import org.openstreetmap.josm.gui.io.UploadDialog;
    2830import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
    2931import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
     
    5759    private static final List<UploadHook> UPLOAD_HOOKS = new LinkedList<>();
    5860    private static final List<UploadHook> LATE_UPLOAD_HOOKS = new LinkedList<>();
    5961
     62    private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload";
     63
    6064    static {
    6165        /**
    6266         * Calls validator before upload.
     
    257261            hook.modifyChangesetTags(changesetTags);
    258262        }
    259263
    260         MainApplication.worker.execute(
    261                 new UploadPrimitivesTask(
    262                         UploadDialog.getUploadDialog().getUploadStrategySpecification(),
    263                         layer,
    264                         apiData,
    265                         cs
    266                 )
    267         );
     264        if (Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).isEmpty() ||
     265                !Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).equalsIgnoreCase("disabled")) {
     266            Optional <AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
     267                    UploadDialog.getUploadDialog().getUploadStrategySpecification(),
     268                    layer,
     269                    apiData,
     270                    cs);
     271
     272            if (asyncUploadTask.isPresent()) {
     273                MainApplication.worker.execute(asyncUploadTask.get());
     274            }
     275        } else {
     276            MainApplication.worker.execute(
     277                    new UploadPrimitivesTask(
     278                            UploadDialog.getUploadDialog().getUploadStrategySpecification(),
     279                            layer,
     280                            apiData,
     281                            cs));
     282        }
    268283    }
    269284
    270285    @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.gui.MainApplication;
     7import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     8import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     9import org.openstreetmap.josm.gui.progress.ProgressTaskId;
     10import org.openstreetmap.josm.gui.util.GuiHelper;
     11import org.openstreetmap.josm.io.UploadStrategySpecification;
     12
     13import javax.swing.*;
     14import java.awt.*;
     15import java.util.Optional;
     16
     17import static org.openstreetmap.josm.tools.I18n.tr;
     18
     19/**
     20 * Task for uploading primitives using background worker threads
     21 * @author udit
     22 */
     23public class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask {
     24
     25    /**
     26     * Static instance
     27     */
     28    private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask = null;
     29
     30    /**
     31     * Member fields
     32     */
     33    private final ProgressTaskId taskId;
     34    private final OsmDataLayer uploadDataLayer;
     35
     36    /**
     37     * Private constructor to restrict creating more Asynchronous upload tasks
     38     *
     39     * @param uploadStrategySpecification
     40     * @param osmDataLayer
     41     * @param apiDataSet
     42     * @param changeset
     43     */
     44    private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification, OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) {
     45        super(uploadStrategySpecification,
     46                osmDataLayer,
     47                apiDataSet,
     48                changeset);
     49
     50        uploadDataLayer = osmDataLayer;
     51        // Create a ProgressTaskId for background upload
     52        taskId = new ProgressTaskId("core", "async-upload");
     53    }
     54
     55    /**
     56     * Creates an instance of AsynchronousUploadPrimitiveTask
     57     *
     58     * @param uploadStrategySpecification
     59     * @param dataLayer
     60     * @param apiDataSet
     61     * @param changeset
     62     * @return Optional<AsynchronousUploadPrimitivesTask>
     63     */
     64    public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask
     65            (UploadStrategySpecification uploadStrategySpecification,
     66             OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) {
     67        synchronized (AsynchronousUploadPrimitivesTask.class) {
     68            if (asynchronousUploadPrimitivesTask != null) {
     69                if (!GraphicsEnvironment.isHeadless()) {
     70                    GuiHelper.runInEDTAndWait(() ->
     71                            JOptionPane.showMessageDialog(MainApplication.parent,
     72                                    tr("A background upload is already in progress. Kindly wait for it to finish before uploading new changes")));
     73                }
     74                return Optional.empty();
     75            } else {
     76                // Create an asynchronous upload task
     77                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
     78                        uploadStrategySpecification,
     79                        dataLayer,
     80                        apiDataSet,
     81                        changeset);
     82                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     83            }
     84        }
     85    }
     86
     87    /**
     88     * Get the current upload task
     89     * @return Optional<AsynchronousUploadPrimitivesTask>
     90     */
     91    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask () {
     92        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     93    }
     94
     95    @Override
     96    public ProgressTaskId canRunInBackground() {
     97        return taskId;
     98    }
     99
     100    @Override
     101    protected void realRun() {
     102        // Lock the data layer before upload in EDT
     103        GuiHelper.runInEDTAndWait(() -> {
     104            // Remove the commands from the undo stack
     105            MainApplication.undoRedo.clean(uploadDataLayer.data);
     106            MainApplication.getLayerManager().prepareLayerForUpload(uploadDataLayer);
     107
     108            // Repainting the Layer List dialog to update the icon of the active layer
     109            LayerListDialog.getInstance().repaint();
     110        });
     111        super.realRun();
     112    }
     113
     114    @Override
     115    protected void cancel() {
     116        super.cancel();
     117        asynchronousUploadPrimitivesTask = null;
     118    }
     119
     120    @Override
     121    protected void finish() {
     122        try {
     123            // Unlock the data layer in EDT
     124            GuiHelper.runInEDTAndWait(() -> {
     125                MainApplication.getLayerManager().processLayerAfterUpload(uploadDataLayer);
     126                LayerListDialog.getInstance().repaint();
     127            });
     128            super.finish();
     129        } finally {
     130            asynchronousUploadPrimitivesTask = null;
     131        }
     132    }
     133}
     134 No newline at end of file
  • src/org/openstreetmap/josm/gui/layer/MainLayerManager.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.layer;
    33
     4import org.openstreetmap.josm.data.osm.DataSet;
     5import org.openstreetmap.josm.gui.MainApplication;
     6import org.openstreetmap.josm.gui.util.GuiHelper;
     7
     8import javax.swing.*;
    49import java.util.ArrayList;
    510import java.util.Collection;
    611import java.util.List;
     
    712import java.util.ListIterator;
    813import java.util.concurrent.CopyOnWriteArrayList;
    914
    10 import org.openstreetmap.josm.data.osm.DataSet;
    11 import org.openstreetmap.josm.gui.util.GuiHelper;
     15import static org.openstreetmap.josm.tools.I18n.tr;
    1216
    1317/**
    1418 * This class extends the layer manager by adding an active and an edit layer.
     
    205209    }
    206210
    207211    /**
    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.
    209214     * @param layer The active layer.
    210215     */
    211216    public void setActiveLayer(final Layer layer) {
    212217        // we force this on to the EDT Thread to make events fire from there.
    213218        // 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        }
    215229    }
    216230
    217231    protected synchronized void realSetActiveLayer(final Layer layer) {
     
    309323     * @return the currently active layer (may be null)
    310324     */
    311325    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        }
    313335    }
    314336
    315337    /**
     
    318340     * @return the current edit layer. May be null.
    319341     */
    320342    public synchronized OsmDataLayer getEditLayer() {
    321         return editLayer;
     343        if (editLayer != null && !editLayer.isReadOnly())
     344            return editLayer;
     345        else
     346            return null;
    322347    }
    323348
    324349    /**
     
    378403        activeLayerChangeListeners.clear();
    379404        layerAvailabilityListeners.clear();
    380405    }
     406
     407    public void prepareLayerForUpload (OsmDataLayer layer) {
     408
     409        GuiHelper.assertCallFromEdt();
     410        layer.setReadOnly();
     411
     412        // Reset only the edit layer as empty
     413        if (editLayer == layer) {
     414            ActiveLayerChangeEvent activeLayerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
     415            editLayer = null;
     416            fireActiveLayerChange(activeLayerChangeEvent);
     417        }
     418    }
     419
     420    public void processLayerAfterUpload (OsmDataLayer layer) {
     421        GuiHelper.assertCallFromEdt();
     422        layer.unsetReadOnly();
     423
     424        // Set the layer as edit layer
     425        // iff the edit layer is empty.
     426        if (editLayer == null) {
     427            ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
     428            editLayer = layer;
     429            fireActiveLayerChange(layerChangeEvent);
     430        }
     431    }
    381432}
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    3131import java.util.Map;
    3232import java.util.Set;
    3333import java.util.concurrent.CopyOnWriteArrayList;
     34import java.util.concurrent.atomic.AtomicBoolean;
    3435import java.util.concurrent.atomic.AtomicInteger;
    3536import java.util.regex.Pattern;
    3637
     
    129130
    130131    private boolean requiresSaveToFile;
    131132    private boolean requiresUploadToServer;
     133    private final AtomicBoolean isReadOnly = new AtomicBoolean(false);
    132134
    133135    /**
    134136     * List of validation errors in this layer.
     
    420422        if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) {
    421423            base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0));
    422424        }
     425
     426        if (isReadOnly()) {
     427            // If the layer is read only then change the default icon to a clock
     428            base = new ImageProvider("clock").setMaxSize(ImageSizes.LAYER);
     429        }
    423430        return base.get();
    424431    }
    425432
     
    11421149        }
    11431150        super.setName(name);
    11441151    }
     1152
     1153    public void setReadOnly() {
     1154        if (!isReadOnly.compareAndSet(false, true)) {
     1155            Logging.warn("Trying to set readOnly flag on a readOnly layer ", this.getName());
     1156        }
     1157    }
     1158
     1159    public void unsetReadOnly() {
     1160        if (!isReadOnly.compareAndSet(true, false)) {
     1161            Logging.warn("Trying to unset readOnly flag on a non-readOnly layer ", this.getName());
     1162        }
     1163    }
     1164
     1165    public boolean isReadOnly() {
     1166        return isReadOnly.get();
     1167    }
    11451168}
  • 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.Changeset;
     9import org.openstreetmap.josm.data.osm.DataSet;
     10import org.openstreetmap.josm.data.osm.Node;
     11import org.openstreetmap.josm.data.osm.Way;
     12import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     13import org.openstreetmap.josm.io.UploadStrategySpecification;
     14import org.openstreetmap.josm.testutils.JOSMTestRules;
     15
     16import java.util.Optional;
     17
     18public class AsynchronousUploadPrimitivesTaskTest {
     19
     20    private UploadStrategySpecification strategy;
     21    private OsmDataLayer layer;
     22    private APIDataSet toUpload;
     23    private Changeset changeset;
     24    private AsynchronousUploadPrimitivesTask uploadPrimitivesTask;
     25
     26    /**
     27     * Setup tests
     28     */
     29    @Rule
     30    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     31    public JOSMTestRules test = new JOSMTestRules();
     32
     33    @Before
     34    public void bootStrap() {
     35        DataSet dataSet = new DataSet();
     36        Node node1 = new Node();
     37        Node node2 = new Node();
     38        node1.setCoor(new LatLon(0, 0));
     39        node2.setCoor(new LatLon(30, 30));
     40        Way way = new Way();
     41        way.addNode(node1);
     42        way.addNode(node2);
     43        dataSet.addPrimitive(node1);
     44        dataSet.addPrimitive(node2);
     45        dataSet.addPrimitive(way);
     46
     47        toUpload = new APIDataSet(dataSet);
     48        layer = new OsmDataLayer(dataSet, "uploadTest", null);
     49        strategy = new UploadStrategySpecification();
     50        changeset = new Changeset();
     51        uploadPrimitivesTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset).get();
     52    }
     53
     54    @After
     55    public void tearDown () {
     56        toUpload = null;
     57        layer = null;
     58        strategy = null;
     59        changeset = null;
     60        uploadPrimitivesTask = null;
     61    }
     62
     63    @Test
     64    public void testSingleUploadInstance () {
     65        Optional<AsynchronousUploadPrimitivesTask> task = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset);
     66        Assert.assertNotNull(uploadPrimitivesTask);
     67        Assert.assertFalse(task.isPresent());
     68    }
     69}