Ticket #8509: async_v7.patch

File async_v7.patch, 18.7 KB (added by udit, 6 years ago)

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

  • 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.
     
    259263            hook.modifyChangesetTags(changesetTags);
    260264        }
    261265
    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        }
    270285    }
    271286
    272287    @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
     13
     14import javax.swing.JOptionPane;
     15import java.awt.GraphicsEnvironment;
     16import java.util.Optional;
     17
     18import 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 */
     30public 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

     
    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.JOptionPane;
    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    /**
    316      * Replies the current edit layer, if any
     338     * Replies the current edit layer, if present and not readOnly
    317339     *
    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    /**
     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    }
    381447}
  • 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    /** Flag used to know if the layer should not be editable */
     134    private final AtomicBoolean isReadOnly = new AtomicBoolean(false);
    132135
    133136    /**
    134137     * List of validation errors in this layer.
     
    420423        if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) {
    421424            base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0));
    422425        }
     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        }
    423431        return base.get();
    424432    }
    425433
     
    11421150        }
    11431151        super.setName(name);
    11441152    }
     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    }
    11451179}
  • 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.Rule;
     6import org.junit.Before;
     7import org.junit.After;
     8import org.junit.Test;
     9import org.junit.Assert;
     10import org.openstreetmap.josm.data.APIDataSet;
     11import org.openstreetmap.josm.data.coor.LatLon;
     12import org.openstreetmap.josm.data.osm.Changeset;
     13import org.openstreetmap.josm.data.osm.DataSet;
     14import org.openstreetmap.josm.data.osm.Node;
     15import org.openstreetmap.josm.data.osm.Way;
     16import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     17import org.openstreetmap.josm.io.UploadStrategySpecification;
     18import org.openstreetmap.josm.testutils.JOSMTestRules;
     19
     20import java.util.Optional;
     21
     22public 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}